Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java')
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java5680
1 files changed, 5680 insertions, 0 deletions
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
new file mode 100644
index 000000000..5b07e4ba9
--- /dev/null
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
@@ -0,0 +1,5680 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2017 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
+ * channingwalton@mac.com - curved line code
+ * gilles.querret@free.fr - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=72995
+ * Max Weninger (max.weninger@windriver.com) - Bug 131895 [Edit] Undo in compare
+ * Max Weninger (max.weninger@windriver.com) - Bug 72936 [Viewers] Show line numbers in comparision
+ * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 178968 [Viewers] Lines scrambled and different font size in compare
+ * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 191524 [Viewers] Synchronize horizontal scrolling by # characters, not % of longest line
+ * Stephan Herrmann (stephan@cs.tu-berlin.de) - Bug 291695: Element compare fails to use source range
+ * Robin Stocker (robin@nibor.org) - Bug 398594: [Edit] Enable center arrow buttons when editable and for both sides
+ * Robin Stocker (robin@nibor.org) - Bug 399960: [Edit] Make merge arrow buttons easier to hit
+ *******************************************************************************/
+package org.eclipse.compare.contentmergeviewer;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareNavigator;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.compare.ICompareNavigator;
+import org.eclipse.compare.IEditableContentExtension;
+import org.eclipse.compare.IEncodedStreamContentAccessor;
+import org.eclipse.compare.INavigatable;
+import org.eclipse.compare.ISharedDocumentAdapter;
+import org.eclipse.compare.IStreamContentAccessor;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.SharedDocumentAdapter;
+import org.eclipse.compare.internal.BufferedCanvas;
+import org.eclipse.compare.internal.ChangeCompareFilterPropertyAction;
+import org.eclipse.compare.internal.ChangePropertyAction;
+import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
+import org.eclipse.compare.internal.CompareEditor;
+import org.eclipse.compare.internal.CompareEditorContributor;
+import org.eclipse.compare.internal.CompareEditorSelectionProvider;
+import org.eclipse.compare.internal.CompareFilterDescriptor;
+import org.eclipse.compare.internal.CompareHandlerService;
+import org.eclipse.compare.internal.CompareMessages;
+import org.eclipse.compare.internal.ComparePreferencePage;
+import org.eclipse.compare.internal.CompareUIPlugin;
+import org.eclipse.compare.internal.DocumentManager;
+import org.eclipse.compare.internal.ICompareContextIds;
+import org.eclipse.compare.internal.ICompareUIConstants;
+import org.eclipse.compare.internal.IMergeViewerTestAdapter;
+import org.eclipse.compare.internal.MergeSourceViewer;
+import org.eclipse.compare.internal.MergeViewerContentProvider;
+import org.eclipse.compare.internal.NavigationEndDialog;
+import org.eclipse.compare.internal.OutlineViewerCreator;
+import org.eclipse.compare.internal.ShowWhitespaceAction;
+import org.eclipse.compare.internal.TextEditorPropertyAction;
+import org.eclipse.compare.internal.Utilities;
+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.RangeDifference;
+import org.eclipse.compare.structuremergeviewer.DiffNode;
+import org.eclipse.compare.structuremergeviewer.DocumentRangeNode;
+import org.eclipse.compare.structuremergeviewer.ICompareInput;
+import org.eclipse.compare.structuremergeviewer.IDiffContainer;
+import org.eclipse.compare.structuremergeviewer.IDiffElement;
+import org.eclipse.core.commands.operations.IOperationHistoryListener;
+import org.eclipse.core.commands.operations.IUndoableOperation;
+import org.eclipse.core.commands.operations.OperationHistoryEvent;
+import org.eclipse.core.commands.operations.OperationHistoryFactory;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Adapters;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.resource.ColorRegistry;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.text.*;
+import org.eclipse.jface.text.source.CompositeRuler;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.util.Util;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.accessibility.AccessibleAdapter;
+import org.eclipse.swt.accessibility.AccessibleEvent;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IKeyBindingService;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.IWorkbenchCommandConstants;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.editors.text.IEncodingSupport;
+import org.eclipse.ui.editors.text.IStorageDocumentProvider;
+import org.eclipse.ui.progress.UIJob;
+import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.ui.texteditor.ChainedPreferenceStore;
+import org.eclipse.ui.texteditor.ChangeEncodingAction;
+import org.eclipse.ui.texteditor.FindReplaceAction;
+import org.eclipse.ui.texteditor.GotoLineAction;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProviderExtension;
+import org.eclipse.ui.texteditor.IElementStateListener;
+import org.eclipse.ui.texteditor.IFindReplaceTargetExtension2;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
+import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
+
+import com.ibm.icu.text.MessageFormat;
+
+/**
+ * A text merge viewer uses the <code>RangeDifferencer</code> to perform
+ * a textual, line-by-line comparison of two (or three) input documents.
+ * It is based on the <code>ContentMergeViewer</code> and uses <code>TextViewer</code>s
+ * to implement the ancestor, left, and right content areas.
+ * <p>
+ * In the three-way compare case ranges of differing lines are highlighted and framed
+ * with different colors to show whether the difference is an incoming, outgoing, or conflicting change.
+ * The <code>TextMergeViewer</code> supports the notion of a current "differing range"
+ * and provides toolbar buttons to navigate from one range to the next (or previous).
+ * <p>
+ * If there is a current "differing range" and the underlying document is editable
+ * the <code>TextMergeViewer</code> enables actions in context menu and toolbar to
+ * copy a range from one side to the other side, thereby performing a merge operation.
+ * <p>
+ * In addition to a line-by-line comparison the <code>TextMergeViewer</code>
+ * uses a token based compare on differing lines.
+ * The token compare is activated when navigating into
+ * a range of differing lines. At first the lines are selected as a block.
+ * When navigating into this block the token compare shows for every line
+ * the differing token by selecting them.
+ * <p>
+ * The <code>TextMergeViewer</code>'s default token compare works on characters separated
+ * by whitespace. If a different strategy is needed (for example, Java tokens in
+ * a Java-aware merge viewer), clients can create their own token
+ * comparators by implementing the <code>ITokenComparator</code> interface and overriding the
+ * <code>TextMergeViewer.createTokenComparator</code> factory method).
+ * <p>
+ * Access to the <code>TextMergeViewer</code>'s model is by means of an
+ * <code>IMergeViewerContentProvider</code>. Its <code>get<it>X</it></code>Content</code> methods must return
+ * either an <code>IDocument</code>, an <code>IDocumentRange</code>, or an <code>IStreamContentAccessor</code>.
+ * In the <code>IDocumentRange</code> case the <code>TextMergeViewer</code>
+ * works on a subrange of a document. In the <code>IStreamContentAccessor</code> case
+ * a document is created internally and initialized from the stream.
+ * <p>
+ * A <code>TextMergeViewer</code> can be used as is. However clients may subclass
+ * to customize the behavior. For example a <code>MergeTextViewer</code> for Java would override
+ * the <code>configureTextViewer</code> method to configure the <code>TextViewer</code> for Java source code,
+ * the <code>createTokenComparator</code> method to create a Java specific tokenizer.
+ * <p>
+ * In 3.5 a new API has been introduced to let clients provide their own source
+ * viewers implementation with an option to configure them basing on a
+ * corresponding editor input.
+ *
+ * @see org.eclipse.compare.rangedifferencer.RangeDifferencer
+ * @see org.eclipse.jface.text.TextViewer
+ * @see ITokenComparator
+ * @see IDocumentRange
+ * @see org.eclipse.compare.IStreamContentAccessor
+ */
+public class TextMergeViewer extends ContentMergeViewer implements IAdaptable {
+ private static final String COPY_LEFT_TO_RIGHT_INDICATOR = ">"; //$NON-NLS-1$
+ private static final String COPY_RIGHT_TO_LEFT_INDICATOR = "<"; //$NON-NLS-1$
+ private static final char ANCESTOR_CONTRIBUTOR = MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR;
+ private static final char RIGHT_CONTRIBUTOR = MergeViewerContentProvider.RIGHT_CONTRIBUTOR;
+ private static final char LEFT_CONTRIBUTOR = MergeViewerContentProvider.LEFT_CONTRIBUTOR;
+
+ private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$
+
+ static final boolean DEBUG= false;
+
+ private static final boolean FIX_47640= true;
+
+ private static final String[] GLOBAL_ACTIONS= {
+ ActionFactory.UNDO.getId(),
+ ActionFactory.REDO.getId(),
+ ActionFactory.CUT.getId(),
+ ActionFactory.COPY.getId(),
+ ActionFactory.PASTE.getId(),
+ ActionFactory.DELETE.getId(),
+ ActionFactory.SELECT_ALL.getId(),
+ ActionFactory.FIND.getId(),
+ ITextEditorActionDefinitionIds.LINE_GOTO
+ };
+ private static final String[] TEXT_ACTIONS= {
+ MergeSourceViewer.UNDO_ID,
+ MergeSourceViewer.REDO_ID,
+ MergeSourceViewer.CUT_ID,
+ MergeSourceViewer.COPY_ID,
+ MergeSourceViewer.PASTE_ID,
+ MergeSourceViewer.DELETE_ID,
+ MergeSourceViewer.SELECT_ALL_ID,
+ MergeSourceViewer.FIND_ID,
+ MergeSourceViewer.GOTO_LINE_ID
+ };
+
+ private static final String BUNDLE_NAME= "org.eclipse.compare.contentmergeviewer.TextMergeViewerResources"; //$NON-NLS-1$
+
+ // the following symbolic constants must match the IDs in Compare's plugin.xml
+ private static final String INCOMING_COLOR= "INCOMING_COLOR"; //$NON-NLS-1$
+ private static final String OUTGOING_COLOR= "OUTGOING_COLOR"; //$NON-NLS-1$
+ private static final String CONFLICTING_COLOR= "CONFLICTING_COLOR"; //$NON-NLS-1$
+ private static final String RESOLVED_COLOR= "RESOLVED_COLOR"; //$NON-NLS-1$
+
+ // constants
+ /** Width of left and right vertical bar */
+ private static final int MARGIN_WIDTH= 6;
+ /** Width of center bar */
+ private static final int CENTER_WIDTH= 34;
+ /** Width of birds eye view */
+ private static final int BIRDS_EYE_VIEW_WIDTH= 12;
+ /** Width of birds eye view */
+ private static final int BIRDS_EYE_VIEW_INSET= 2;
+ /** */
+ private static final int RESOLVE_SIZE= 5;
+
+ /** line width of change borders */
+ private static final int LW= 1;
+
+ private boolean fShowCurrentOnly= false;
+ private boolean fShowCurrentOnly2= false;
+ private int fMarginWidth= MARGIN_WIDTH;
+ private int fTopInset;
+
+ // Colors
+ private RGB fBackground;
+ private RGB fForeground;
+
+ private boolean fIsUsingSystemForeground= true;
+ private boolean fIsUsingSystemBackground= true;
+
+ private RGB SELECTED_INCOMING;
+ private RGB INCOMING;
+ private RGB INCOMING_FILL;
+ private RGB INCOMING_TEXT_FILL;
+
+ private RGB SELECTED_CONFLICT;
+ private RGB CONFLICT;
+ private RGB CONFLICT_FILL;
+ private RGB CONFLICT_TEXT_FILL;
+
+ private RGB SELECTED_OUTGOING;
+ private RGB OUTGOING;
+ private RGB OUTGOING_FILL;
+ private RGB OUTGOING_TEXT_FILL;
+
+ private RGB RESOLVED;
+
+ private IPreferenceStore fPreferenceStore;
+ private IPropertyChangeListener fPreferenceChangeListener;
+
+ private HashMap<Object, Position> fNewAncestorRanges= new HashMap<>();
+ private HashMap<Object, Position> fNewLeftRanges= new HashMap<>();
+ private HashMap<Object, Position> fNewRightRanges= new HashMap<>();
+
+ private MergeSourceViewer fAncestor;
+ private MergeSourceViewer fLeft;
+ private MergeSourceViewer fRight;
+
+ private int fLeftLineCount;
+ private int fRightLineCount;
+
+ private boolean fInScrolling;
+
+ private int fPts[]= new int[8]; // scratch area for polygon drawing
+
+ private int fInheritedDirection; // inherited direction
+ private int fTextDirection; // requested direction for embedded SourceViewer
+
+ private ActionContributionItem fIgnoreAncestorItem;
+ private boolean fHighlightRanges;
+
+ private boolean fShowPseudoConflicts= false;
+
+ private boolean fUseSplines= true;
+ private boolean fUseSingleLine= true;
+ private boolean fHighlightTokenChanges = false;
+
+ private String fSymbolicFontName;
+
+ private ActionContributionItem fNextDiff; // goto next difference
+ private ActionContributionItem fPreviousDiff; // goto previous difference
+ private ActionContributionItem fCopyDiffLeftToRightItem;
+ private ActionContributionItem fCopyDiffRightToLeftItem;
+
+ private CompareHandlerService fHandlerService;
+
+ private boolean fSynchronizedScrolling= true;
+
+ private MergeSourceViewer fFocusPart;
+
+ private boolean fSubDoc= true;
+ private IPositionUpdater fPositionUpdater;
+ private boolean fIsMotif;
+ private boolean fIsCarbon;
+ private boolean fIsMac;
+
+ private boolean fHasErrors;
+
+
+ // SWT widgets
+ private BufferedCanvas fAncestorCanvas;
+ private BufferedCanvas fLeftCanvas;
+ private BufferedCanvas fRightCanvas;
+ private Canvas fScrollCanvas;
+ private ScrollBar fVScrollBar;
+ private Canvas fBirdsEyeCanvas;
+ private Canvas fSummaryHeader;
+ private HeaderPainter fHeaderPainter;
+
+ // SWT resources to be disposed
+ private Map<RGB, Color> fColors;
+ private Cursor fBirdsEyeCursor;
+
+ // points for center curves
+ private double[] fBasicCenterCurve;
+
+ private Button fLeftToRightButton;
+ private Button fRightToLeftButton;
+ private Diff fButtonDiff;
+
+ private ContributorInfo fLeftContributor;
+ private ContributorInfo fRightContributor;
+ private ContributorInfo fAncestorContributor;
+ private int isRefreshing = 0;
+ private int fSynchronziedScrollPosition;
+ private ActionContributionItem fNextChange;
+ private ActionContributionItem fPreviousChange;
+ private ShowWhitespaceAction showWhitespaceAction;
+ private InternalOutlineViewerCreator fOutlineViewerCreator;
+ private TextEditorPropertyAction toggleLineNumbersAction;
+ private IFindReplaceTarget fFindReplaceTarget;
+ private ChangePropertyAction fIgnoreWhitespace;
+ private List<ChangeCompareFilterPropertyAction> fCompareFilterActions = new ArrayList<>();
+ private DocumentMerger fMerger;
+ /** The current diff */
+ private Diff fCurrentDiff;
+ private Diff fSavedDiff;
+
+ // Bug 259362 - Update diffs after undo
+ private boolean copyOperationInProgress = false;
+ private IUndoableOperation copyUndoable = null;
+ private IOperationHistoryListener operationHistoryListener;
+
+ /**
+ * Preference key for highlighting current line.
+ */
+ private final static String CURRENT_LINE= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE;
+ /**
+ * Preference key for highlight color of current line.
+ */
+ private final static String CURRENT_LINE_COLOR= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR;
+
+ private List<SourceViewerDecorationSupport> fSourceViewerDecorationSupport = new ArrayList<>(3);
+ // whether enhanced viewer configuration has been done
+ private boolean isConfigured = false;
+ private boolean fRedoDiff = false;
+
+ private final class InternalOutlineViewerCreator extends OutlineViewerCreator implements ISelectionChangedListener {
+ @Override
+ public Viewer findStructureViewer(Viewer oldViewer,
+ ICompareInput input, Composite parent,
+ CompareConfiguration configuration) {
+ if (input != getInput())
+ return null;
+ final Viewer v = CompareUI.findStructureViewer(oldViewer, input, parent, configuration);
+ if (v != null) {
+ v.getControl().addDisposeListener(e -> v.removeSelectionChangedListener(InternalOutlineViewerCreator.this));
+ v.addSelectionChangedListener(this);
+ }
+
+ return v;
+ }
+
+ @Override
+ public boolean hasViewerFor(Object input) {
+ return true;
+ }
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ ISelection s = event.getSelection();
+ if (s instanceof IStructuredSelection) {
+ IStructuredSelection ss = (IStructuredSelection) s;
+ Object element = ss.getFirstElement();
+ Diff diff = findDiff(element);
+ if (diff != null)
+ setCurrentDiff(diff, true);
+ }
+ }
+
+ private Diff findDiff(Object element) {
+ if (element instanceof ICompareInput) {
+ ICompareInput ci = (ICompareInput) element;
+ Position p = getPosition(ci.getLeft());
+ if (p != null)
+ return findDiff(p, true);
+ p = getPosition(ci.getRight());
+ if (p != null)
+ return findDiff(p, false);
+ }
+ return null;
+ }
+
+ private Diff findDiff(Position p, boolean left) {
+ for (Iterator<?> iterator = fMerger.rangesIterator(); iterator.hasNext();) {
+ Diff diff = (Diff) iterator.next();
+ Position diffPos;
+ if (left) {
+ diffPos = diff.getPosition(LEFT_CONTRIBUTOR);
+ } else {
+ diffPos = diff.getPosition(RIGHT_CONTRIBUTOR);
+ }
+ // If the element falls within a diff, highlight that diff
+ 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)
+ return diff;
+ }
+ return null;
+ }
+
+ private Position getPosition(ITypedElement left) {
+ if (left instanceof DocumentRangeNode) {
+ DocumentRangeNode drn = (DocumentRangeNode) left;
+ return drn.getRange();
+ }
+ return null;
+ }
+
+ @Override
+ public Object getInput() {
+ return TextMergeViewer.this.getInput();
+ }
+ }
+
+ class ContributorInfo implements IElementStateListener, VerifyListener, IDocumentListener, IEncodingSupport {
+ private final TextMergeViewer fViewer;
+ private final Object fElement;
+ private char fLeg;
+ private String fEncoding;
+ private IDocumentProvider fDocumentProvider;
+ private IEditorInput fDocumentKey;
+ private ISelection fSelection;
+ private int fTopIndex = -1;
+ private boolean fNeedsValidation = false;
+ private MergeSourceViewer fSourceViewer;
+
+ public ContributorInfo(TextMergeViewer viewer, Object element, char leg) {
+ fViewer = viewer;
+ fElement = element;
+ fLeg = leg;
+ if (fElement instanceof IEncodedStreamContentAccessor) {
+ try {
+ fEncoding = ((IEncodedStreamContentAccessor) fElement).getCharset();
+ } catch (CoreException e) {
+ // silently ignored
+ }
+ }
+ }
+
+ @Override
+ public void setEncoding(String encoding) {
+ if (fDocumentKey == null || fDocumentProvider == null) {
+ return;
+ }
+ if (fDocumentProvider instanceof IStorageDocumentProvider) {
+ IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider;
+ String current = provider.getEncoding(fDocumentKey);
+ boolean dirty = fDocumentProvider.canSaveDocument(fDocumentKey);
+ if (!dirty) {
+ String internal = encoding == null ? "" : encoding; //$NON-NLS-1$
+ if (!internal.equals(current)) {
+ provider.setEncoding(fDocumentKey, encoding);
+ try {
+ fDocumentProvider.resetDocument(fDocumentKey);
+ } catch (CoreException e) {
+ CompareUIPlugin.log(e);
+ } finally {
+ update(true);
+ updateStructure(fLeg);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getEncoding() {
+ if (fDocumentProvider != null && fDocumentKey != null
+ && fDocumentProvider instanceof IStorageDocumentProvider) {
+ IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider;
+ return provider.getEncoding(fDocumentKey);
+ }
+ return null;
+ }
+
+ @Override
+ public String getDefaultEncoding() {
+ if (fDocumentProvider != null && fDocumentKey != null
+ && fDocumentProvider instanceof IStorageDocumentProvider) {
+ IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider;
+ return provider.getDefaultEncoding();
+ }
+ return null;
+ }
+
+ private String internalGetEncoding() {
+ if (fElement instanceof IEncodedStreamContentAccessor) {
+ try {
+ fEncoding = ((IEncodedStreamContentAccessor) fElement)
+ .getCharset();
+ } catch (CoreException e) {
+ // silently ignored
+ }
+ }
+ if (fEncoding != null) {
+ return fEncoding;
+ }
+ return ResourcesPlugin.getEncoding();
+ }
+
+ public void setEncodingIfAbsent(ContributorInfo otherContributor) {
+ if (fEncoding == null)
+ fEncoding = otherContributor.fEncoding;
+ }
+
+ public IDocument getDocument() {
+ if (fDocumentProvider != null) {
+ IDocument document = fDocumentProvider.getDocument(getDocumentKey());
+ if (document != null)
+ return document;
+ }
+ if (fElement instanceof IDocument)
+ return (IDocument) fElement;
+ if (fElement instanceof IDocumentRange)
+ return ((IDocumentRange) fElement).getDocument();
+ if (fElement instanceof IStreamContentAccessor)
+ return DocumentManager.get(fElement);
+ return null;
+ }
+
+ public void setDocument(MergeSourceViewer viewer, boolean isEditable) {
+ // Ensure that this method is only called once
+ Assert.isTrue(fSourceViewer == null);
+ fSourceViewer = viewer;
+ try {
+ internalSetDocument(viewer);
+ } catch (RuntimeException e) {
+ // The error may be due to a stale entry in the DocumentManager (see bug 184489)
+ clearCachedDocument();
+ throw e;
+ }
+ setEditable(viewer.getSourceViewer(), isEditable);
+ // Verify changes if the document is editable
+ if (isEditable) {
+ fNeedsValidation = true;
+ viewer.getSourceViewer().getTextWidget().addVerifyListener(this);
+ }
+ }
+
+ /*
+ * Returns true if a new Document could be installed.
+ */
+ private boolean internalSetDocument(MergeSourceViewer tp) {
+
+ if (tp == null)
+ return false;
+
+ IDocument newDocument = null;
+ Position range= null;
+
+ if (fElement instanceof IDocumentRange) {
+ newDocument= ((IDocumentRange) fElement).getDocument();
+ range= ((IDocumentRange) fElement).getRange();
+ connectToSharedDocument();
+
+ } else if (fElement instanceof IDocument) {
+ newDocument= (IDocument) fElement;
+ setupDocument(newDocument);
+
+ } else if (fElement instanceof IStreamContentAccessor) {
+ newDocument= DocumentManager.get(fElement);
+ if (newDocument == null) {
+ newDocument = createDocument();
+ DocumentManager.put(fElement, newDocument);
+ setupDocument(newDocument);
+ } else if (fDocumentProvider == null) {
+ // Connect to a shared document so we can get the proper save synchronization
+ connectToSharedDocument();
+ }
+ } else if (fElement == null) { // deletion on one side
+
+ ITypedElement parent= this.fViewer.getParent(fLeg); // we try to find an insertion position within the deletion's parent
+
+ if (parent instanceof IDocumentRange) {
+ newDocument= ((IDocumentRange) parent).getDocument();
+ newDocument.addPositionCategory(DIFF_RANGE_CATEGORY);
+ Object input= this.fViewer.getInput();
+ range= this.fViewer.getNewRange(fLeg, input);
+ if (range == null) {
+ int pos= 0;
+ if (input instanceof ICompareInput)
+ pos= this.fViewer.findInsertionPosition(fLeg, (ICompareInput) input);
+ range= new Position(pos, 0);
+ try {
+ newDocument.addPosition(DIFF_RANGE_CATEGORY, range);
+ } catch (BadPositionCategoryException ex) {
+ // silently ignored
+ if (TextMergeViewer.DEBUG) System.out.println("BadPositionCategoryException: " + ex); //$NON-NLS-1$
+ } catch (BadLocationException ex) {
+ // silently ignored
+ if (TextMergeViewer.DEBUG) System.out.println("BadLocationException: " + ex); //$NON-NLS-1$
+ }
+ this.fViewer.addNewRange(fLeg, input, range);
+ }
+ } else if (parent instanceof IDocument) {
+ newDocument= ((IDocumentRange) fElement).getDocument();
+ }
+ }
+
+ boolean enabled= true;
+ if (newDocument == null) {
+ newDocument= new Document(""); //$NON-NLS-1$
+ enabled= false;
+ }
+
+ // Update the viewer document or range
+ IDocument oldDoc= tp.getSourceViewer().getDocument();
+ if (newDocument != oldDoc) {
+ updateViewerDocument(tp, newDocument, range);
+ } else { // same document but different range
+ updateViewerDocumentRange(tp, range);
+ }
+ newDocument.addDocumentListener(this);
+
+ tp.setEnabled(enabled);
+
+ return enabled;
+ }
+
+ /*
+ * The viewer document is the same but the range has changed
+ */
+ private void updateViewerDocumentRange(MergeSourceViewer tp, Position range) {
+ tp.setRegion(range);
+ if (this.fViewer.fSubDoc) {
+ if (range != null) {
+ IRegion r= this.fViewer.normalizeDocumentRegion(tp.getSourceViewer().getDocument(), TextMergeViewer.toRegion(range));
+ tp.getSourceViewer().setVisibleRegion(r.getOffset(), r.getLength());
+ } else
+ tp.getSourceViewer().resetVisibleRegion();
+ } else
+ tp.getSourceViewer().resetVisibleRegion();
+ }
+
+ /*
+ * The viewer has a new document
+ */
+ private void updateViewerDocument(MergeSourceViewer tp, IDocument document, Position range) {
+ unsetDocument(tp);
+ if (document == null)
+ return;
+
+ connectPositionUpdater(document);
+
+ // install new document
+ tp.setRegion(range);
+ SourceViewer sourceViewer = tp.getSourceViewer();
+ sourceViewer.setRedraw(false);
+ try {
+ if (this.fViewer.fSubDoc && range != null) {
+ IRegion r= this.fViewer.normalizeDocumentRegion(document, TextMergeViewer.toRegion(range));
+ sourceViewer.setDocument(document, r.getOffset(), r.getLength());
+ } else {
+ sourceViewer.setDocument(document);
+ }
+ } finally {
+ sourceViewer.setRedraw(true);
+ }
+
+ tp.rememberDocument(document);
+ }
+
+ void connectPositionUpdater(IDocument document) {
+ document.addPositionCategory(DIFF_RANGE_CATEGORY);
+ if (this.fViewer.fPositionUpdater == null)
+ this.fViewer.fPositionUpdater= this.fViewer.new ChildPositionUpdater(DIFF_RANGE_CATEGORY);
+ else
+ document.removePositionUpdater(this.fViewer.fPositionUpdater);
+ document.addPositionUpdater(this.fViewer.fPositionUpdater);
+ }
+
+ private void unsetDocument(MergeSourceViewer tp) {
+ IDocument oldDoc= internalGetDocument(tp);
+ if (oldDoc != null) {
+ tp.rememberDocument(null);
+ try {
+ oldDoc.removePositionCategory(DIFF_RANGE_CATEGORY);
+ } catch (BadPositionCategoryException ex) {
+ // Ignore
+ }
+ if (fPositionUpdater != null)
+ oldDoc.removePositionUpdater(fPositionUpdater);
+ oldDoc.removeDocumentListener(this);
+ }
+ }
+
+ private IDocument createDocument() {
+ // If the content provider is a text content provider, attempt to obtain
+ // a shared document (i.e. file buffer)
+ IDocument newDoc = connectToSharedDocument();
+
+ if (newDoc == null) {
+ IStreamContentAccessor sca= (IStreamContentAccessor) fElement;
+ String s= null;
+
+ try {
+ String encoding = internalGetEncoding();
+ s = Utilities.readString(sca, encoding);
+ } catch (CoreException ex) {
+ this.fViewer.setError(fLeg, ex.getMessage());
+ }
+
+ newDoc= new Document(s != null ? s : ""); //$NON-NLS-1$
+ }
+ return newDoc;
+ }
+
+ /**
+ * Connect to a shared document if possible. Return <code>null</code>
+ * if the connection was not possible.
+ * @return the shared document or <code>null</code> if connection to a
+ * shared document was not possible
+ */
+ private IDocument connectToSharedDocument() {
+ IEditorInput key = getDocumentKey();
+ if (key != null) {
+ if (fDocumentProvider != null) {
+ // We've already connected and setup the document
+ return fDocumentProvider.getDocument(key);
+ }
+ IDocumentProvider documentProvider = getDocumentProvider();
+ if (documentProvider != null) {
+ try {
+ connect(documentProvider, key);
+ setCachedDocumentProvider(key,
+ documentProvider);
+ IDocument newDoc = documentProvider.getDocument(key);
+ this.fViewer.updateDirtyState(key, documentProvider, fLeg);
+ return newDoc;
+ } catch (CoreException e) {
+ // Connection failed. Log the error and continue without a shared document
+ CompareUIPlugin.log(e);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void connect(IDocumentProvider documentProvider, IEditorInput input) throws CoreException {
+ final ISharedDocumentAdapter sda = Adapters.adapt(fElement, ISharedDocumentAdapter.class);
+ if (sda != null) {
+ sda.connect(documentProvider, input);
+ } else {
+ documentProvider.connect(input);
+ }
+ }
+
+ private void disconnect(IDocumentProvider provider, IEditorInput input) {
+ final ISharedDocumentAdapter sda = Adapters.adapt(fElement, ISharedDocumentAdapter.class);
+ if (sda != null) {
+ sda.disconnect(provider, input);
+ } else {
+ provider.disconnect(input);
+ }
+ }
+
+ private void setCachedDocumentProvider(IEditorInput key,
+ IDocumentProvider documentProvider) {
+ fDocumentKey = key;
+ fDocumentProvider = documentProvider;
+ documentProvider.addElementStateListener(this);
+ }
+
+ public void disconnect() {
+ IDocumentProvider provider = null;
+ IEditorInput input = getDocumentKey();
+ synchronized(this) {
+ if (fDocumentProvider != null) {
+ provider = fDocumentProvider;
+ fDocumentProvider = null;
+ fDocumentKey = null;
+ }
+ }
+ if (provider != null) {
+ disconnect(provider, input);
+ provider.removeElementStateListener(this);
+ }
+ // If we have a listener registered with the widget, remove it
+ if (fSourceViewer != null) {
+ StyledText textWidget = fSourceViewer.getSourceViewer().getTextWidget();
+ if (textWidget != null && !textWidget.isDisposed()) {
+ if (fNeedsValidation) {
+ fSourceViewer.getSourceViewer().getTextWidget().removeVerifyListener(this);
+ fNeedsValidation = false;
+ }
+ IDocument oldDoc= internalGetDocument(fSourceViewer);
+ if (oldDoc != null) {
+ oldDoc.removeDocumentListener(this);
+ }
+ }
+ }
+ clearCachedDocument();
+ }
+
+ private void clearCachedDocument() {
+ // Finally, remove the document from the document manager
+ IDocument doc = DocumentManager.get(fElement);
+ if (doc != null)
+ DocumentManager.remove(doc);
+ }
+
+ private IDocument internalGetDocument(MergeSourceViewer tp) {
+ IDocument oldDoc= tp.getSourceViewer().getDocument();
+ if (oldDoc == null) {
+ oldDoc= tp.getRememberedDocument();
+ }
+ return oldDoc;
+ }
+
+ /**
+ * Return the document key used to obtain a shared document. A <code>null</code>
+ * is returned in the following cases:
+ * <ol>
+ * <li>This contributor does not have a shared document adapter.</li>
+ * <li>This text merge viewer has a document partitioner but uses the default partitioning.</li>
+ * <li>This text merge viewer does not use he default content provider.</li>
+ * </ol>
+ * @return the document key used to obtain a shared document or <code>null</code>
+ */
+ private IEditorInput getDocumentKey() {
+ if (fDocumentKey != null)
+ return fDocumentKey;
+ if (isUsingDefaultContentProvider() && fElement != null && canHaveSharedDocument()) {
+ ISharedDocumentAdapter sda = Adapters.adapt(fElement, ISharedDocumentAdapter.class);
+ if (sda != null) {
+ return sda.getDocumentKey(fElement);
+ }
+ }
+ return null;
+ }
+
+ private IDocumentProvider getDocumentProvider() {
+ if (fDocumentProvider != null)
+ return fDocumentProvider;
+ // We will only use document providers if the content provider is the
+ // default content provider
+ if (isUsingDefaultContentProvider()) {
+ IEditorInput input = getDocumentKey();
+ if (input != null)
+ return SharedDocumentAdapter.getDocumentProvider(input);
+ }
+ return null;
+ }
+
+ private boolean isUsingDefaultContentProvider() {
+ return fViewer.isUsingDefaultContentProvider();
+ }
+
+ private boolean canHaveSharedDocument() {
+ return fViewer.canHaveSharedDocument();
+ }
+
+ boolean hasSharedDocument(Object object) {
+ return (fElement == object &&
+ fDocumentProvider != null
+ && fDocumentProvider.getDocument(getDocumentKey()) != null);
+ }
+
+ public boolean flush() throws CoreException {
+ if (fDocumentProvider != null) {
+ IEditorInput input = getDocumentKey();
+ IDocument document = fDocumentProvider.getDocument(input);
+ if (document != null) {
+ final ISharedDocumentAdapter sda = Adapters.adapt(fElement, ISharedDocumentAdapter.class);
+ if (sda != null) {
+ sda.flushDocument(fDocumentProvider, input, document, false);
+ return true;
+ }
+ try {
+ fDocumentProvider.aboutToChange(input);
+ fDocumentProvider.saveDocument(new NullProgressMonitor(), input, document, false);
+ return true;
+ } finally {
+ fDocumentProvider.changed(input);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void elementMoved(Object originalElement, Object movedElement) {
+ IEditorInput input = getDocumentKey();
+ if (input != null && input.equals(originalElement)) {
+ // This method will only get called if the buffer is not dirty
+ resetDocument();
+ }
+ }
+
+ @Override
+ public void elementDirtyStateChanged(Object element, boolean isDirty) {
+ if (!checkState())
+ return;
+ IEditorInput input = getDocumentKey();
+ if (input != null && input.equals(element)) {
+ this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg);
+ }
+ }
+
+ @Override
+ public void elementDeleted(Object element) {
+ IEditorInput input = getDocumentKey();
+ if (input != null && input.equals(element)) {
+ // This method will only get called if the buffer is not dirty
+ resetDocument();
+ }
+ }
+ private void resetDocument() {
+ // Need to remove the document from the manager before refreshing
+ // or the old document will still be found
+ clearCachedDocument();
+ // TODO: This is fine for now but may need to be revisited if a refresh is performed
+ // higher up as well (e.g. perhaps a refresh request that waits until after all parties
+ // have been notified).
+ if (checkState())
+ fViewer.refresh();
+ }
+
+ private boolean checkState() {
+ if (fViewer == null)
+ return false;
+ Control control = fViewer.getControl();
+ if (control == null)
+ return false;
+ return !control.isDisposed();
+ }
+
+ @Override
+ public void elementContentReplaced(Object element) {
+ if (!checkState())
+ return;
+ IEditorInput input = getDocumentKey();
+ if (input != null && input.equals(element)) {
+ this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg);
+
+ // recalculate diffs and update controls
+ new UIJob(CompareMessages.DocumentMerger_0) {
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ update(true);
+ updateStructure(fLeg);
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+ }
+ @Override
+ public void elementContentAboutToBeReplaced(Object element) {
+ // Nothing to do
+ }
+
+ public Object getElement() {
+ return fElement;
+ }
+
+ public void cacheSelection(MergeSourceViewer viewer) {
+ if (viewer == null) {
+ this.fSelection = null;
+ this.fTopIndex = -1;
+ } else {
+ this.fSelection = viewer.getSourceViewer().getSelection();
+ this.fTopIndex = viewer.getSourceViewer().getTopIndex();
+ }
+ }
+
+ public void updateSelection(MergeSourceViewer viewer, boolean includeScroll) {
+ if (fSelection != null)
+ viewer.getSourceViewer().setSelection(fSelection);
+ if (includeScroll && fTopIndex != -1) {
+ viewer.getSourceViewer().setTopIndex(fTopIndex);
+ }
+ }
+
+ public void transferContributorStateFrom(
+ ContributorInfo oldContributor) {
+ if (oldContributor != null) {
+ fSelection = oldContributor.fSelection;
+ fTopIndex = oldContributor.fTopIndex;
+ fEncoding = oldContributor.fEncoding;
+ }
+
+ }
+
+ public boolean validateChange() {
+ if (fElement == null)
+ return true;
+ if (fDocumentProvider instanceof IDocumentProviderExtension) {
+ IDocumentProviderExtension ext = (IDocumentProviderExtension) fDocumentProvider;
+ if (ext.isReadOnly(fDocumentKey)) {
+ try {
+ ext.validateState(fDocumentKey, getControl().getShell());
+ ext.updateStateCache(fDocumentKey);
+ } catch (CoreException e) {
+ ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_12, CompareMessages.TextMergeViewer_13, e.getStatus());
+ return false;
+ }
+ }
+ return !ext.isReadOnly(fDocumentKey);
+ }
+ IEditableContentExtension ext = Adapters.adapt(fElement, IEditableContentExtension.class);
+ if (ext != null) {
+ if (ext.isReadOnly()) {
+ IStatus status = ext.validateEdit(getControl().getShell());
+ if (!status.isOK()) {
+ if (status.getSeverity() == IStatus.ERROR) {
+ ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_14, CompareMessages.TextMergeViewer_15, status);
+ return false;
+ }
+ if (status.getSeverity() == IStatus.CANCEL)
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void verifyText(VerifyEvent e) {
+ if (!validateChange()) {
+ e.doit= false;
+ }
+ }
+
+ @Override
+ public void documentAboutToBeChanged(DocumentEvent e) {
+ // nothing to do
+ }
+
+ @Override
+ public void documentChanged(DocumentEvent e) {
+ boolean dirty = true;
+ if (fDocumentProvider != null && fDocumentKey != null) {
+ dirty = fDocumentProvider.canSaveDocument(fDocumentKey);
+ }
+ TextMergeViewer.this.documentChanged(e, dirty);
+ // Remove our verify listener since the document is now dirty
+ if (fNeedsValidation && fSourceViewer != null && !fSourceViewer.getSourceViewer().getTextWidget().isDisposed()) {
+ fSourceViewer.getSourceViewer().getTextWidget().removeVerifyListener(this);
+ fNeedsValidation = false;
+ }
+ }
+ }
+
+ class HeaderPainter implements PaintListener {
+ private static final int INSET= BIRDS_EYE_VIEW_INSET;
+
+ private RGB fIndicatorColor;
+ private Color fSeparatorColor;
+
+ public HeaderPainter() {
+ fSeparatorColor= fSummaryHeader.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
+ }
+
+ /*
+ * Returns true on color change
+ */
+ public boolean setColor(RGB color) {
+ RGB oldColor= fIndicatorColor;
+ fIndicatorColor= color;
+ if (color == null)
+ return oldColor != null;
+ if (oldColor != null)
+ return !color.equals(oldColor);
+ return true;
+ }
+
+ private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topLeft, Color bottomRight) {
+ gc.setForeground(topLeft);
+ gc.drawLine(x, y, x + w -1, y);
+ gc.drawLine(x, y, x, y + h -1);
+
+ gc.setForeground(bottomRight);
+ gc.drawLine(x + w, y, x + w, y + h);
+ gc.drawLine(x, y + h, x + w, y + h);
+ }
+
+ @Override
+ public void paintControl(PaintEvent e) {
+ Point s= fSummaryHeader.getSize();
+
+ if (fIndicatorColor != null) {
+ Display d= fSummaryHeader.getDisplay();
+ e.gc.setBackground(getColor(d, fIndicatorColor));
+ int min= Math.min(s.x, s.y)-2*INSET;
+ Rectangle r= new Rectangle((s.x-min)/2, (s.y-min)/2, min, min);
+ e.gc.fillRectangle(r);
+ if (d != null)
+ drawBevelRect(e.gc, r.x, r.y, r.width -1, r.height -1, d.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW), d.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
+
+ e.gc.setForeground(fSeparatorColor);
+ e.gc.setLineWidth(0 /* 1 */);
+ e.gc.drawLine(0+1, s.y-1, s.x-1-1, s.y-1);
+ }
+ }
+ }
+
+ /*
+ * The position updater used to adapt the positions representing
+ * the child document ranges to changes of the parent document.
+ */
+ class ChildPositionUpdater extends DefaultPositionUpdater {
+ /*
+ * Creates the position updated.
+ */
+ protected ChildPositionUpdater(String category) {
+ super(category);
+ }
+
+ /*
+ * Child document ranges cannot be deleted other then by calling
+ * freeChildDocument.
+ */
+ @Override
+ protected boolean notDeleted() {
+ return true;
+ }
+
+ /*
+ * If an insertion happens at a child document's start offset, the
+ * position is extended rather than shifted. Also, if something is added
+ * right behind the end of the position, the position is extended rather
+ * than kept stable.
+ */
+ @Override
+ protected void adaptToInsert() {
+ if (fPosition == fLeft.getRegion() || fPosition == fRight.getRegion()) {
+ int myStart= fPosition.offset;
+ int myEnd= fPosition.offset + fPosition.length;
+ myEnd= Math.max(myStart, myEnd);
+
+ int yoursStart= fOffset;
+ int yoursEnd= fOffset + fReplaceLength -1;
+ yoursEnd= Math.max(yoursStart, yoursEnd);
+
+ if (myEnd < yoursStart)
+ return;
+
+ if (myStart <= yoursStart)
+ fPosition.length += fReplaceLength;
+ else
+ fPosition.offset += fReplaceLength;
+ } else {
+ super.adaptToInsert();
+ }
+ }
+ }
+
+ private class ChangeHighlighter implements ITextPresentationListener {
+ private final MergeSourceViewer viewer;
+
+ public ChangeHighlighter(MergeSourceViewer viewer) {
+ this.viewer = viewer;
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ private StyleRange getStyleRange(Diff diff, IRegion region) {
+ //Color cText = getColor(null, getTextColor());
+ Color cTextFill = getColor(null, getTextFillColor(diff));
+ if (cTextFill == null)
+ return null;
+ Position p = diff.getPosition(getLeg(viewer));
+ int start = p.getOffset();
+ int length = p.getLength();
+ // Don't start before the region
+ if (start < region.getOffset()) {
+ length = length - (region.getOffset() - start);
+ start = region.getOffset();
+ }
+ // Don't go past the end of the region
+ int regionEnd = region.getOffset() + region.getLength();
+ if (start + length > regionEnd) {
+ length = regionEnd - start;
+ }
+ if (length < 0)
+ return null;
+
+ return new StyleRange(start, length, null, cTextFill);
+ }
+
+ private RGB getTextFillColor(Diff diff) {
+ if (isThreeWay() && !isIgnoreAncestor()) {
+ switch (diff.getKind()) {
+ case RangeDifference.RIGHT:
+ return getCompareConfiguration().isMirrored() ? OUTGOING_TEXT_FILL : INCOMING_TEXT_FILL;
+ case RangeDifference.ANCESTOR:
+ case RangeDifference.CONFLICT:
+ return CONFLICT_TEXT_FILL;
+ case RangeDifference.LEFT:
+ return getCompareConfiguration().isMirrored() ? INCOMING_TEXT_FILL : OUTGOING_TEXT_FILL;
+ default:
+ return null;
+ }
+ }
+ return OUTGOING_TEXT_FILL;
+ }
+ }
+
+ private class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension2, IFindReplaceTargetExtension3 {
+ @Override
+ public boolean canPerformFind() {
+ return fFocusPart != null;
+ }
+
+ @Override
+ public int findAndSelect(int widgetOffset, String findString,
+ boolean searchForward, boolean caseSensitive, boolean wholeWord) {
+ return fFocusPart.getSourceViewer().getFindReplaceTarget().findAndSelect(widgetOffset, findString, searchForward, caseSensitive, wholeWord);
+ }
+
+ @Override
+ public Point getSelection() {
+ return fFocusPart.getSourceViewer().getFindReplaceTarget().getSelection();
+ }
+
+ @Override
+ public String getSelectionText() {
+ return fFocusPart.getSourceViewer().getFindReplaceTarget().getSelectionText();
+ }
+
+ @Override
+ public boolean isEditable() {
+ return fFocusPart.getSourceViewer().getFindReplaceTarget().isEditable();
+ }
+
+ @Override
+ public void replaceSelection(String text) {
+ fFocusPart.getSourceViewer().getFindReplaceTarget().replaceSelection(text);
+ }
+
+ @Override
+ public int findAndSelect(int offset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension3) {
+ return ((IFindReplaceTargetExtension3) findReplaceTarget).findAndSelect(offset, findString, searchForward, caseSensitive, wholeWord, regExSearch);
+ }
+
+ // fallback like in org.eclipse.ui.texteditor.FindReplaceTarget
+ if (!regExSearch && findReplaceTarget != null)
+ return findReplaceTarget.findAndSelect(offset, findString, searchForward, caseSensitive, wholeWord);
+ return -1;
+ }
+
+ @Override
+ public void replaceSelection(String text, boolean regExReplace) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension3) {
+ ((IFindReplaceTargetExtension3) findReplaceTarget).replaceSelection(text, regExReplace);
+ return;
+ }
+
+ // fallback like in org.eclipse.ui.texteditor.FindReplaceTarget
+ if (!regExReplace && findReplaceTarget != null)
+ findReplaceTarget.replaceSelection(text);
+ }
+
+ @Override
+ public boolean validateTargetState() {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension2) {
+ return ((IFindReplaceTargetExtension2) findReplaceTarget).validateTargetState();
+ }
+ return true;
+ }
+
+ @Override
+ public void beginSession() {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).beginSession();
+ }
+ }
+
+ @Override
+ public void endSession() {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).endSession();
+ }
+ }
+
+ @Override
+ public IRegion getScope() {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ return ((IFindReplaceTargetExtension) findReplaceTarget).getScope();
+ }
+ return null;
+ }
+
+ @Override
+ public void setScope(IRegion scope) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).setScope(scope);
+ }
+ }
+
+ @Override
+ public Point getLineSelection() {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ return ((IFindReplaceTargetExtension) findReplaceTarget).getLineSelection();
+ }
+ return null;
+ }
+
+ @Override
+ public void setSelection(int offset, int length) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).setSelection(offset, length);
+ }
+ }
+
+ @Override
+ public void setScopeHighlightColor(Color color) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).setScopeHighlightColor(color);
+ }
+ }
+
+ @Override
+ public void setReplaceAllMode(boolean replaceAll) {
+ IFindReplaceTarget findReplaceTarget = fFocusPart.getSourceViewer().getFindReplaceTarget();
+ if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
+ ((IFindReplaceTargetExtension) findReplaceTarget).setReplaceAllMode(replaceAll);
+ }
+ }
+
+ }
+
+ //---- MergeTextViewer
+
+ /**
+ * Creates a text merge viewer under the given parent control.
+ *
+ * @param parent the parent control
+ * @param configuration the configuration object
+ */
+ public TextMergeViewer(Composite parent, CompareConfiguration configuration) {
+ this(parent, SWT.NULL, configuration);
+ }
+
+ /**
+ * Creates a text merge viewer under the given parent control.
+ *
+ * @param parent the parent control
+ * @param style SWT style bits for top level composite of this viewer
+ * @param configuration the configuration object
+ */
+ public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
+ super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
+
+ operationHistoryListener = event -> TextMergeViewer.this.historyNotification(event);
+ OperationHistoryFactory.getOperationHistory()
+ .addOperationHistoryListener(operationHistoryListener);
+
+ fMerger = new DocumentMerger(new IDocumentMergerInput() {
+ @Override
+ public ITokenComparator createTokenComparator(String line) {
+ return TextMergeViewer.this.createTokenComparator(line);
+ }
+ @Override
+ public CompareConfiguration getCompareConfiguration() {
+ return TextMergeViewer.this.getCompareConfiguration();
+ }
+ @Override
+ public IDocument getDocument(char contributor) {
+ switch (contributor) {
+ case LEFT_CONTRIBUTOR:
+ return fLeft.getSourceViewer().getDocument();
+ case RIGHT_CONTRIBUTOR:
+ return fRight.getSourceViewer().getDocument();
+ case ANCESTOR_CONTRIBUTOR:
+ return fAncestor.getSourceViewer().getDocument();
+ default:
+ return null;
+ }
+ }
+ @Override
+ public int getHunkStart() {
+ return TextMergeViewer.this.getHunkStart();
+ }
+ @Override
+ 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();
+ default:
+ return null;
+ }
+ }
+ @Override
+ public boolean isHunkOnLeft() {
+ ITypedElement left = ((ICompareInput) getInput()).getRight();
+ return left != null && Adapters.adapt(left, IHunk.class) != null;
+ }
+ @Override
+ public boolean isIgnoreAncestor() {
+ return TextMergeViewer.this.isIgnoreAncestor();
+ }
+ @Override
+ public boolean isPatchHunk() {
+ return TextMergeViewer.this.isPatchHunk();
+ }
+
+ @Override
+ public boolean isShowPseudoConflicts() {
+ return fShowPseudoConflicts;
+ }
+ @Override
+ public boolean isThreeWay() {
+ return TextMergeViewer.this.isThreeWay();
+ }
+ @Override
+ public boolean isPatchHunkOk() {
+ return TextMergeViewer.this.isPatchHunkOk();
+ }
+ });
+
+ int inheritedStyle= parent.getStyle();
+ if ((inheritedStyle & SWT.LEFT_TO_RIGHT) != 0)
+ fInheritedDirection= SWT.LEFT_TO_RIGHT;
+ else if ((inheritedStyle & SWT.RIGHT_TO_LEFT) != 0)
+ fInheritedDirection= SWT.RIGHT_TO_LEFT;
+ else
+ fInheritedDirection= SWT.NONE;
+
+ if ((style & SWT.LEFT_TO_RIGHT) != 0)
+ fTextDirection= SWT.LEFT_TO_RIGHT;
+ else if ((style & SWT.RIGHT_TO_LEFT) != 0)
+ fTextDirection= SWT.RIGHT_TO_LEFT;
+ else
+ fTextDirection= SWT.NONE;
+
+ fSymbolicFontName= getSymbolicFontName();
+
+ fIsMotif= Util.isMotif();
+ fIsCarbon= Util.isCarbon();
+ fIsMac= Util.isMac();
+
+ if (fIsMotif)
+ fMarginWidth= 0;
+
+ fPreferenceChangeListener= event -> TextMergeViewer.this.handlePropertyChangeEvent(event);
+
+ fPreferenceStore= createChainedPreferenceStore();
+ if (fPreferenceStore != null) {
+ fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
+
+ fSynchronizedScrolling= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
+ fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);
+ //fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
+ fUseSingleLine= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE);
+ fHighlightTokenChanges= fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES);
+ //fUseResolveUI= fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI);
+ }
+
+ buildControl(parent);
+
+ setColors();
+
+ INavigatable nav= new INavigatable() {
+ @Override
+ public boolean selectChange(int flag) {
+ if (flag == INavigatable.FIRST_CHANGE || flag == INavigatable.LAST_CHANGE) {
+ selectFirstDiff(flag == INavigatable.FIRST_CHANGE);
+ return false;
+ }
+ return navigate(flag == INavigatable.NEXT_CHANGE, false, false);
+ }
+ @Override
+ public Object getInput() {
+ return TextMergeViewer.this.getInput();
+ }
+ @Override
+ public boolean openSelectedChange() {
+ return false;
+ }
+ @Override
+ public boolean hasChange(int flag) {
+ return getNextVisibleDiff(flag == INavigatable.NEXT_CHANGE, false) != null;
+ }
+ };
+ fComposite.setData(INavigatable.NAVIGATOR_PROPERTY, nav);
+
+ fBirdsEyeCursor= new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
+
+ JFaceResources.getFontRegistry().addListener(fPreferenceChangeListener);
+ JFaceResources.getColorRegistry().addListener(fPreferenceChangeListener);
+ updateFont();
+ }
+
+ private static class LineNumberRulerToggleAction extends TextEditorPropertyAction {
+ public LineNumberRulerToggleAction(String label, MergeSourceViewer[] viewers, String preferenceKey) {
+ super(label, viewers, preferenceKey);
+ }
+
+ @Override
+ protected boolean toggleState(boolean checked) {
+ return true;
+ }
+ }
+
+ private ChainedPreferenceStore createChainedPreferenceStore() {
+ List<IPreferenceStore> stores= new ArrayList<>(2);
+ stores.add(getCompareConfiguration().getPreferenceStore());
+ stores.add(EditorsUI.getPreferenceStore());
+ return new ChainedPreferenceStore(stores.toArray(new IPreferenceStore[stores.size()]));
+ }
+
+ /**
+ * Creates a color from the information stored in the given preference store.
+ * Returns <code>null</code> if there is no such information available.
+ * @param store preference store
+ * @param key preference key
+ * @return the color or <code>null</code>
+ */
+ private static RGB createColor(IPreferenceStore store, String key) {
+ if (!store.contains(key))
+ return null;
+ if (store.isDefault(key))
+ return PreferenceConverter.getDefaultColor(store, key);
+ return PreferenceConverter.getColor(store, key);
+ }
+
+ private void setColors() {
+ fIsUsingSystemBackground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT);
+ if (!fIsUsingSystemBackground)
+ fBackground= createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND);
+
+ fIsUsingSystemForeground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT);
+ if (!fIsUsingSystemForeground)
+ fForeground= createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND);
+
+ updateColors(null);
+ }
+
+ private String getSymbolicFontName() {
+ Class<?> clazz= getClass();
+ do {
+ String fontName= clazz.getName();
+ if (JFaceResources.getFontRegistry().hasValueFor(fontName))
+ return fontName;
+ clazz= clazz.getSuperclass();
+ } while (clazz != null);
+ // use text compare font if no font has been registered for subclass
+ return getClass().getName();
+ }
+
+ private void updateFont() {
+ Font f= JFaceResources.getFont(fSymbolicFontName);
+ if (f != null) {
+ if (fAncestor != null)
+ fAncestor.setFont(f);
+ if (fLeft != null)
+ fLeft.setFont(f);
+ if (fRight != null)
+ fRight.setFont(f);
+ }
+ }
+
+ private void checkForColorUpdate(Display display) {
+ if (fIsUsingSystemBackground) {
+ RGB bg= display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
+ if (!bg.equals(getBackground(display))) {
+ updateColors(display);
+ }
+ }
+ }
+
+ /**
+ * Sets the viewer's background color to the given RGB value.
+ * If the value is <code>null</code> the system's default background color is used.
+ * @param background the background color or <code>null</code> to use the system's default background color
+ * @since 2.0
+ */
+ public void setBackgroundColor(RGB background) {
+ fIsUsingSystemBackground= (background == null);
+ fBackground= background;
+ updateColors(null);
+ }
+
+ private RGB getBackground(Display display) {
+ if (fBackground != null)
+ return fBackground;
+
+ if (display == null)
+ display= fComposite.getDisplay();
+
+ return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
+ }
+
+ /**
+ * Sets the viewer's foreground color to the given RGB value.
+ * If the value is <code>null</code> the system's default foreground color is used.
+ * @param foreground the foreground color or <code>null</code> to use the system's default foreground color
+ * @since 2.0
+ */
+ public void setForegroundColor(RGB foreground) {
+ fIsUsingSystemForeground= (foreground == null);
+ fForeground= foreground;
+ updateColors(null);
+ }
+
+ private void updateColors(Display display) {
+
+ if (display == null)
+ display = fComposite.getDisplay();
+
+ Color bgColor = null;
+ if (fBackground != null)
+ bgColor = getColor(display, fBackground);
+
+ if (fAncestor != null)
+ fAncestor.setBackgroundColor(bgColor);
+ if (fLeft != null)
+ fLeft.setBackgroundColor(bgColor);
+ if (fRight != null)
+ fRight.setBackgroundColor(bgColor);
+
+ Color fgColor = null;
+ if (fForeground != null)
+ fgColor = getColor(display, fForeground);
+
+ if (fAncestor != null)
+ fAncestor.setForegroundColor(fgColor);
+ if (fLeft != null)
+ fLeft.setForegroundColor(fgColor);
+ if (fRight != null)
+ fRight.setForegroundColor(fgColor);
+
+ ColorRegistry registry= JFaceResources.getColorRegistry();
+
+ RGB bg= getBackground(display);
+ SELECTED_INCOMING= registry.getRGB(INCOMING_COLOR);
+ if (SELECTED_INCOMING == null)
+ SELECTED_INCOMING= new RGB(0, 0, 255); // BLUE
+ INCOMING= interpolate(SELECTED_INCOMING, bg, 0.6);
+ INCOMING_FILL= interpolate(SELECTED_INCOMING, bg, 0.97);
+ INCOMING_TEXT_FILL= interpolate(SELECTED_INCOMING, bg, 0.85);
+
+ SELECTED_OUTGOING= registry.getRGB(OUTGOING_COLOR);
+ if (SELECTED_OUTGOING == null)
+ SELECTED_OUTGOING= new RGB(0, 0, 0); // BLACK
+ OUTGOING= interpolate(SELECTED_OUTGOING, bg, 0.6);
+ OUTGOING_FILL= interpolate(SELECTED_OUTGOING, bg, 0.97);
+ OUTGOING_TEXT_FILL= interpolate(SELECTED_OUTGOING, bg, 0.85);
+
+ SELECTED_CONFLICT= registry.getRGB(CONFLICTING_COLOR);
+ if (SELECTED_CONFLICT == null)
+ SELECTED_CONFLICT= new RGB(255, 0, 0); // RED
+ CONFLICT= interpolate(SELECTED_CONFLICT, bg, 0.6);
+ CONFLICT_FILL= interpolate(SELECTED_CONFLICT, bg, 0.97);
+ CONFLICT_TEXT_FILL= interpolate(SELECTED_CONFLICT, bg, 0.85);
+
+ RESOLVED= registry.getRGB(RESOLVED_COLOR);
+ if (RESOLVED == null)
+ RESOLVED= new RGB(0, 255, 0); // GREEN
+
+ updatePresentation();
+ }
+
+ private void updatePresentation() {
+ refreshBirdsEyeView();
+ invalidateLines();
+ invalidateTextPresentation();
+ }
+
+ /**
+ * Invalidates the current presentation by invalidating the three text viewers.
+ * @since 2.0
+ */
+ public void invalidateTextPresentation() {
+ if (fAncestor != null)
+ fAncestor.getSourceViewer().invalidateTextPresentation();
+ if (fLeft != null)
+ fLeft.getSourceViewer().invalidateTextPresentation();
+ if (fRight != null)
+ fRight.getSourceViewer().invalidateTextPresentation();
+ }
+
+ /**
+ * Configures the passed text viewer. This method is called after the three
+ * text viewers have been created for the content areas. The
+ * <code>TextMergeViewer</code> implementation of this method will
+ * configure the viewer with a {@link SourceViewerConfiguration}.
+ * Subclasses may reimplement to provide a specific configuration for the
+ * text viewer.
+ *
+ * @param textViewer
+ * the text viewer to configure
+ */
+ protected void configureTextViewer(TextViewer textViewer) {
+ // to get undo for all text files, bugzilla 131895, 33665
+ if(textViewer instanceof ISourceViewer){
+ SourceViewerConfiguration configuration= new SourceViewerConfiguration();
+ ((ISourceViewer) textViewer).configure(configuration);
+ }
+ }
+
+ /**
+ * Creates an <code>ITokenComparator</code> which is used to show the
+ * intra line differences.
+ * The <code>TextMergeViewer</code> implementation of this method returns a
+ * tokenizer that breaks a line into words separated by whitespace.
+ * Subclasses may reimplement to provide a specific tokenizer.
+ * @param line the line for which to create the <code>ITokenComparator</code>
+ * @return a ITokenComparator which is used for a second level token compare.
+ */
+ protected ITokenComparator createTokenComparator(String line) {
+ return new TokenComparator(line);
+ }
+
+ /**
+ * Setup the given document for use with this viewer. By default,
+ * the partitioner returned from {@link #getDocumentPartitioner()}
+ * is registered as the default partitioner for the document.
+ * Subclasses that return a partitioner must also override
+ * {@link #getDocumentPartitioning()} if they wish to be able to use shared
+ * documents (i.e. file buffers).
+ * @param document the document to be set up
+ *
+ * @since 3.3
+ */
+ protected void setupDocument(IDocument document) {
+ String partitioning = getDocumentPartitioning();
+ if (partitioning == null || !(document instanceof IDocumentExtension3)) {
+ if (document.getDocumentPartitioner() == null) {
+ IDocumentPartitioner partitioner= getDocumentPartitioner();
+ if (partitioner != null) {
+ document.setDocumentPartitioner(partitioner);
+ partitioner.connect(document);
+ }
+ }
+ } else {
+ IDocumentExtension3 ex3 = (IDocumentExtension3) document;
+ if (ex3.getDocumentPartitioner(partitioning) == null) {
+ IDocumentPartitioner partitioner= getDocumentPartitioner();
+ if (partitioner != null) {
+ ex3.setDocumentPartitioner(partitioning, partitioner);
+ partitioner.connect(document);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a document partitioner which is suitable for the underlying content type.
+ * This method is only called if the input provided by the content provider is a
+ * <code>IStreamContentAccessor</code> and an internal document must be obtained. This
+ * document is initialized with the partitioner returned from this method.
+ * <p>
+ * The <code>TextMergeViewer</code> implementation of this method returns
+ * <code>null</code>. Subclasses may reimplement to create a partitioner for a
+ * specific content type. Subclasses that do return a partitioner should also
+ * return a partitioning from {@link #getDocumentPartitioning()} in order to make
+ * use of shared documents (e.g. file buffers).
+ *
+ * @return a document partitioner, or <code>null</code>
+ */
+ protected IDocumentPartitioner getDocumentPartitioner() {
+ return null;
+ }
+
+ /**
+ * Return the partitioning to which the partitioner returned from
+ * {@link #getDocumentPartitioner()} is to be associated. Return <code>null</code>
+ * only if partitioning is not needed or if the subclass
+ * overrode {@link #setupDocument(IDocument)} directly.
+ * By default, <code>null</code> is returned which means that shared
+ * documents that return a partitioner from {@link #getDocumentPartitioner()}
+ * will not be able to use shared documents.
+ * @see IDocumentExtension3
+ * @return a partitioning
+ *
+ * @since 3.3
+ */
+ protected String getDocumentPartitioning() {
+ return null;
+ }
+
+ /**
+ * Called on the viewer disposal.
+ * Unregisters from the compare configuration.
+ * Clients may extend if they have to do additional cleanup.
+ * @param event
+ */
+ @Override
+ protected void handleDispose(DisposeEvent event) {
+ OperationHistoryFactory.getOperationHistory().removeOperationHistoryListener(operationHistoryListener);
+
+ if (fHandlerService != null)
+ fHandlerService.dispose();
+
+ Object input= getInput();
+ removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, input);
+ removeFromDocumentManager(LEFT_CONTRIBUTOR, input);
+ removeFromDocumentManager(RIGHT_CONTRIBUTOR, input);
+
+ if (DEBUG)
+ DocumentManager.dump();
+
+ if (fPreferenceChangeListener != null) {
+ JFaceResources.getFontRegistry().removeListener(fPreferenceChangeListener);
+ JFaceResources.getColorRegistry().removeListener(fPreferenceChangeListener);
+ if (fPreferenceStore != null)
+ fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
+ fPreferenceChangeListener= null;
+ }
+
+ fLeftCanvas= null;
+ fRightCanvas= null;
+ fVScrollBar= null;
+ fBirdsEyeCanvas= null;
+ fSummaryHeader= null;
+
+ fAncestorContributor.unsetDocument(fAncestor);
+ fLeftContributor.unsetDocument(fLeft);
+ fRightContributor.unsetDocument(fRight);
+
+ disconnect(fLeftContributor);
+ disconnect(fRightContributor);
+ disconnect(fAncestorContributor);
+
+ if (fBirdsEyeCursor != null) {
+ fBirdsEyeCursor.dispose();
+ fBirdsEyeCursor= null;
+ }
+
+ if (showWhitespaceAction != null)
+ showWhitespaceAction.dispose();
+
+ if (toggleLineNumbersAction != null)
+ toggleLineNumbersAction.dispose();
+
+ if (fIgnoreWhitespace != null)
+ fIgnoreWhitespace.dispose();
+
+ getCompareConfiguration().setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTERS_INITIALIZING,
+ Boolean.TRUE);
+ disposeCompareFilterActions(false);
+
+ if (fSourceViewerDecorationSupport != null) {
+ for (Iterator<SourceViewerDecorationSupport> iterator = fSourceViewerDecorationSupport.iterator(); iterator.hasNext();) {
+ iterator.next().dispose();
+ }
+ fSourceViewerDecorationSupport = null;
+ }
+
+
+ if (fAncestor != null)
+ fAncestor.dispose();
+ fAncestor = null;
+ if (fLeft != null)
+ fLeft.dispose();
+ fLeft = null;
+ if (fRight != null)
+ fRight.dispose();
+ fRight = null;
+
+ if (fColors != null) {
+ Iterator<Color> i= fColors.values().iterator();
+ while (i.hasNext()) {
+ Color color= i.next();
+ if (!color.isDisposed())
+ color.dispose();
+ }
+ fColors= null;
+ }
+ // don't add anything here, disposing colors should be done last
+ super.handleDispose(event);
+ }
+
+ private void disconnect(ContributorInfo legInfo) {
+ if (legInfo != null)
+ legInfo.disconnect();
+ }
+
+ //-------------------------------------------------------------------------------------------------------------
+ //--- internal ------------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------------------------------------------------
+
+ /*
+ * Creates the specific SWT controls for the content areas.
+ * Clients must not call or override this method.
+ */
+ @Override
+ protected void createControls(Composite composite) {
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.TEXT_MERGE_VIEW);
+
+ // 1st row
+ if (fMarginWidth > 0) {
+ fAncestorCanvas= new BufferedCanvas(composite, SWT.NONE) {
+ @Override
+ public void doPaint(GC gc) {
+ paintSides(gc, fAncestor, fAncestorCanvas, false);
+ }
+ };
+ fAncestorCanvas.addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent e) {
+ setCurrentDiff2(handleMouseInSides(fAncestorCanvas, fAncestor, e.y), false);
+ }
+ }
+ );
+ }
+
+ fAncestor= createPart(composite);
+ setEditable(fAncestor.getSourceViewer(), false);
+ fAncestor.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
+ @Override
+ public void getName(AccessibleEvent e) {
+ e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_ancestor, getCompareConfiguration().getAncestorLabel(getInput()));
+ }
+ });
+ fAncestor.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fAncestor));
+
+ fSummaryHeader= new Canvas(composite, SWT.NONE);
+ fHeaderPainter= new HeaderPainter();
+ fSummaryHeader.addPaintListener(fHeaderPainter);
+ updateResolveStatus();
+
+ // 2nd row
+ if (fMarginWidth > 0) {
+ fLeftCanvas= new BufferedCanvas(composite, SWT.NONE) {
+ @Override
+ public void doPaint(GC gc) {
+ paintSides(gc, fLeft, fLeftCanvas, false);
+ }
+ };
+ fLeftCanvas.addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent e) {
+ setCurrentDiff2(handleMouseInSides(fLeftCanvas, fLeft, e.y), false);
+ }
+ }
+ );
+ }
+
+ fLeft= createPart(composite);
+ fLeft.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
+ fLeft.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
+ @Override
+ public void getName(AccessibleEvent e) {
+ e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_left, getCompareConfiguration().getLeftLabel(getInput()));
+ }
+ });
+ fLeft.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fLeft));
+
+ fRight= createPart(composite);
+ fRight.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
+ fRight.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
+ @Override
+ public void getName(AccessibleEvent e) {
+ e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_right, getCompareConfiguration().getRightLabel(getInput()));
+ }
+ });
+ fRight.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fRight));
+
+ IWorkbenchPart part = getCompareConfiguration().getContainer().getWorkbenchPart();
+ // part is not available for contexts different than editor
+ if (part != null) {
+ ISelectionProvider selectionProvider = part.getSite().getSelectionProvider();
+ if (selectionProvider instanceof CompareEditorSelectionProvider) {
+ CompareEditorSelectionProvider cesp = (CompareEditorSelectionProvider) selectionProvider;
+ SourceViewer focusSourceViewer = fFocusPart == null ? null : fFocusPart.getSourceViewer();
+ cesp.setViewers(new SourceViewer[] { fLeft.getSourceViewer(), fRight.getSourceViewer(), fAncestor.getSourceViewer() }, focusSourceViewer);
+ }
+ }
+
+ hsynchViewport(fAncestor.getSourceViewer(), fLeft.getSourceViewer(), fRight.getSourceViewer());
+ hsynchViewport(fLeft.getSourceViewer(), fAncestor.getSourceViewer(), fRight.getSourceViewer());
+ hsynchViewport(fRight.getSourceViewer(), fAncestor.getSourceViewer(), fLeft.getSourceViewer());
+
+ if (fMarginWidth > 0) {
+ fRightCanvas= new BufferedCanvas(composite, SWT.NONE) {
+ @Override
+ public void doPaint(GC gc) {
+ paintSides(gc, fRight, fRightCanvas, fSynchronizedScrolling);
+ }
+ };
+ fRightCanvas.addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent e) {
+ setCurrentDiff2(handleMouseInSides(fRightCanvas, fRight, e.y), false);
+ }
+ }
+ );
+ }
+
+ fScrollCanvas= new Canvas(composite, SWT.V_SCROLL);
+ Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0);
+ fTopInset= trim.y;
+
+ fVScrollBar= fScrollCanvas.getVerticalBar();
+ fVScrollBar.setIncrement(1);
+ fVScrollBar.setVisible(true);
+ fVScrollBar.addListener(SWT.Selection,
+ e -> {
+ int vpos= ((ScrollBar) e.widget).getSelection();
+ synchronizedScrollVertical(vpos);
+ }
+ );
+
+ fBirdsEyeCanvas= new BufferedCanvas(composite, SWT.NONE) {
+ @Override
+ public void doPaint(GC gc) {
+ paintBirdsEyeView(this, gc);
+ }
+ };
+ fBirdsEyeCanvas.addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent e) {
+ setCurrentDiff2(handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y), true);
+ }
+ }
+ );
+ fBirdsEyeCanvas.addMouseMoveListener(
+ new MouseMoveListener() {
+ private Cursor fLastCursor;
+
+ @Override
+ public void mouseMove(MouseEvent e) {
+ Cursor cursor= null;
+ Diff diff= handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y);
+ if (diff != null && diff.getKind() != RangeDifference.NOCHANGE)
+ cursor= fBirdsEyeCursor;
+ if (fLastCursor != cursor) {
+ fBirdsEyeCanvas.setCursor(cursor);
+ fLastCursor= cursor;
+ }
+ }
+ }
+ );
+
+ IWorkbenchPart workbenchPart = getCompareConfiguration().getContainer().getWorkbenchPart();
+ if (workbenchPart != null) {
+ IContextService service = workbenchPart.getSite().getService(IContextService.class);
+ if (service != null) {
+ service.activateContext("org.eclipse.ui.textEditorScope"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private void hsynchViewport(final TextViewer tv1, final TextViewer tv2, final TextViewer tv3) {
+ final StyledText st1= tv1.getTextWidget();
+ final StyledText st2= tv2.getTextWidget();
+ final StyledText st3= tv3.getTextWidget();
+ final ScrollBar sb1= st1.getHorizontalBar();
+ sb1.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (fSynchronizedScrolling) {
+ int v= sb1.getSelection();
+ if (st2.isVisible())
+ st2.setHorizontalPixel(v);
+ if (st3.isVisible())
+ st3.setHorizontalPixel(v);
+ }
+ }
+ });
+ }
+
+ private void setCurrentDiff2(Diff diff, boolean reveal) {
+ if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) {
+ //fCurrentDiff= null;
+ setCurrentDiff(diff, reveal);
+ }
+ }
+
+ private Diff handleMouseInSides(Canvas canvas, MergeSourceViewer tp, int my) {
+
+ int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight();
+ int visibleHeight= tp.getViewportHeight();
+
+ if (! fHighlightRanges)
+ return null;
+
+ if (fMerger.hasChanges()) {
+ int shift= tp.getVerticalScrollOffset() + (2-LW);
+
+ Point region= new Point(0, 0);
+ 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(leg), region);
+ int y= (region.x * lineHeight) + shift;
+ int h= region.y * lineHeight;
+
+ if (y+h < 0)
+ continue;
+ if (y >= visibleHeight)
+ break;
+
+ if (my >= y && my < y+h)
+ return diff;
+ }
+ }
+ return null;
+ }
+
+ private Diff getDiffUnderMouse(Canvas canvas, int mx, int my, Rectangle r) {
+
+ if (! fSynchronizedScrolling)
+ return null;
+
+ int lineHeight= fLeft.getSourceViewer().getTextWidget().getLineHeight();
+ int visibleHeight= fRight.getViewportHeight();
+
+ Point size= canvas.getSize();
+ int w= size.x;
+
+ if (! fHighlightRanges)
+ return null;
+
+ if (fMerger.hasChanges()) {
+ int lshift= fLeft.getVerticalScrollOffset();
+ int rshift= fRight.getVerticalScrollOffset();
+
+ Point region= new Point(0, 0);
+
+ for (Iterator<?> iterator = fMerger.changesIterator(); iterator.hasNext();) {
+ Diff diff = (Diff) iterator.next();
+ if (diff.isDeleted())
+ continue;
+
+ if (fShowCurrentOnly2 && !isCurrentDiff(diff))
+ continue;
+
+ fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
+ int ly= (region.x * lineHeight) + lshift;
+ int lh= region.y * lineHeight;
+
+ fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
+ int ry= (region.x * lineHeight) + rshift;
+ int rh= region.y * lineHeight;
+
+ if (Math.max(ly+lh, ry+rh) < 0)
+ continue;
+ if (Math.min(ly, ry) >= visibleHeight)
+ break;
+
+ int SIZE= fIsCarbon ? 30 : 20;
+ int cx= (w-SIZE)/2;
+ int cy= ((ly+lh/2) + (ry+rh/2) - SIZE)/2;
+ if (my >= cy && my < cy+SIZE && mx >= cx && mx < cx+SIZE) {
+ if (r != null) {
+ r.x= cx;
+ r.y= cy;
+ r.width= SIZE;
+ r.height= SIZE;
+ }
+ return diff;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Diff handlemouseInBirdsEyeView(Canvas canvas, int my) {
+ return fMerger.findDiff(getViewportHeight(), fSynchronizedScrolling, canvas.getSize(), my);
+ }
+
+ private void paintBirdsEyeView(Canvas canvas, GC gc) {
+
+ Color c;
+ Rectangle r= new Rectangle(0, 0, 0, 0);
+ int yy, hh;
+
+ Point size= canvas.getSize();
+
+ int virtualHeight= fSynchronizedScrolling ? fMerger.getVirtualHeight() : fMerger.getRightHeight();
+ if (virtualHeight < getViewportHeight())
+ return;
+
+ Display display= canvas.getDisplay();
+ int y= 0;
+ for (Iterator<?> iterator = fMerger.rangesIterator(); iterator.hasNext();) {
+ Diff diff = (Diff) iterator.next();
+ int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
+ : diff.getRightHeight();
+
+ if (fMerger.useChange(diff)) {
+
+ 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;
+ }
+ }
+
+ private void refreshBirdsEyeView() {
+ if (fBirdsEyeCanvas != null)
+ fBirdsEyeCanvas.redraw();
+ }
+
+ /**
+ * Override to give focus to the pane that previously had focus or to a suitable
+ * default pane.
+ * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleSetFocus()
+ * @since 3.3
+ */
+ @Override
+ protected boolean handleSetFocus() {
+ if (fRedoDiff) {
+ new UIJob(CompareMessages.DocumentMerger_0) {
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ update(true);
+ updateStructure();
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ fRedoDiff = false;
+ }
+ if (fFocusPart == null) {
+ if (fLeft != null && fLeft.getEnabled()) {
+ fFocusPart= fLeft;
+ } else if (fRight != null && fRight.getEnabled()) {
+ fFocusPart= fRight;
+ } else if (fAncestor != null && fAncestor.getEnabled()) {
+ fFocusPart= fAncestor;
+ }
+ }
+ if (fFocusPart != null) {
+ StyledText st= fFocusPart.getSourceViewer().getTextWidget();
+ if (st != null)
+ return st.setFocus();
+ }
+ return false; // could not set focus
+ }
+
+
+ class HoverResizer extends Resizer {
+ Canvas fCanvas;
+ public HoverResizer(Canvas c, int dir) {
+ super(c, dir);
+ fCanvas= c;
+ }
+ @Override
+ public void mouseMove(MouseEvent e) {
+ if (!fIsDown && fUseSingleLine && isAnySideEditable() && handleMouseMoveOverCenter(fCanvas, e.x, e.y))
+ return;
+ super.mouseMove(e);
+ }
+ }
+
+ @Override
+ protected final Control createCenterControl(Composite parent) {
+ if (fSynchronizedScrolling) {
+ final Canvas canvas= new BufferedCanvas(parent, SWT.NONE) {
+ @Override
+ public void doPaint(GC gc) {
+ paintCenter(this, gc);
+ }
+ };
+ new HoverResizer(canvas, HORIZONTAL);
+
+ if (fNormalCursor == null) fNormalCursor= new Cursor(canvas.getDisplay(), SWT.CURSOR_ARROW);
+ int style= fIsMac ? SWT.FLAT : SWT.PUSH;
+
+ fLeftToRightButton= new Button(canvas, style);
+ fLeftToRightButton.setCursor(fNormalCursor);
+ fLeftToRightButton.setText(COPY_LEFT_TO_RIGHT_INDICATOR);
+ fLeftToRightButton.setToolTipText(
+ Utilities.getString(getResourceBundle(), "action.CopyDiffLeftToRight.tooltip")); //$NON-NLS-1$
+ fLeftToRightButton.pack();
+ fLeftToRightButton.setVisible(false);
+ fLeftToRightButton.addSelectionListener(
+ new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ handleCenterButtonSelection(true);
+ }
+ }
+ );
+
+ fRightToLeftButton= new Button(canvas, style);
+ fRightToLeftButton.setCursor(fNormalCursor);
+ fRightToLeftButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR);
+ fRightToLeftButton.setToolTipText(
+ Utilities.getString(getResourceBundle(), "action.CopyDiffRightToLeft.tooltip")); //$NON-NLS-1$
+ fRightToLeftButton.pack();
+ fRightToLeftButton.setVisible(false);
+ fRightToLeftButton.addSelectionListener(
+ new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ handleCenterButtonSelection(false);
+ }
+ }
+ );
+
+ return canvas;
+ }
+ return super.createCenterControl(parent);
+ }
+
+ private void handleCenterButtonSelection(boolean leftToRight) {
+ fLeftToRightButton.setVisible(false);
+ fRightToLeftButton.setVisible(false);
+ if (fButtonDiff != null) {
+ setCurrentDiff(fButtonDiff, false);
+ copy(fCurrentDiff, leftToRight, false);
+ }
+ }
+
+ private boolean handleMouseMoveOverCenter(Canvas canvas, int x, int y) {
+ Rectangle r= new Rectangle(0, 0, 0, 0);
+ Diff diff= getDiffUnderMouse(canvas, x, y, r);
+ if (diff != fButtonDiff) {
+ if (diff != null) {
+ fButtonDiff= diff;
+ boolean leftEditable= fLeft.getSourceViewer().isEditable();
+ boolean rightEditable= fRight.getSourceViewer().isEditable();
+ if (leftEditable && rightEditable) {
+ int height= r.height;
+ int leftToRightY= r.y - height/2;
+ int rightToLeftY= leftToRightY + height;
+ Rectangle bounds = canvas.getBounds();
+ if (leftToRightY < 0) {
+ // button must not be hidden at top
+ leftToRightY= 0;
+ rightToLeftY= height;
+ } else if (rightToLeftY + height > bounds.height) {
+ // button must not be hidden at bottom
+ leftToRightY= bounds.height - height - height;
+ rightToLeftY= leftToRightY + height;
+ }
+ Rectangle leftToRightBounds= new Rectangle(r.x, leftToRightY, r.width, r.height);
+ fLeftToRightButton.setBounds(leftToRightBounds);
+ fLeftToRightButton.setVisible(true);
+ Rectangle rightToLeftBounds= new Rectangle(r.x, rightToLeftY, r.width, r.height);
+ fRightToLeftButton.setBounds(rightToLeftBounds);
+ fRightToLeftButton.setVisible(true);
+ } else if (leftEditable) {
+ fRightToLeftButton.setBounds(r);
+ fRightToLeftButton.setVisible(true);
+ fLeftToRightButton.setVisible(false);
+ } else if (rightEditable) {
+ fLeftToRightButton.setBounds(r);
+ fLeftToRightButton.setVisible(true);
+ fRightToLeftButton.setVisible(false);
+ } else
+ fButtonDiff= null;
+ } else {
+ fRightToLeftButton.setVisible(false);
+ fLeftToRightButton.setVisible(false);
+ fButtonDiff= null;
+ }
+ }
+ return fButtonDiff != null;
+ }
+
+ @Override
+ protected final int getCenterWidth() {
+ if (fSynchronizedScrolling)
+ return CENTER_WIDTH;
+ return super.getCenterWidth();
+ }
+
+ private int getDirection() {
+ switch (fTextDirection) {
+ case SWT.LEFT_TO_RIGHT:
+ case SWT.RIGHT_TO_LEFT:
+ if (fInheritedDirection == fTextDirection)
+ return SWT.NONE;
+ return fTextDirection;
+ default:
+ return fInheritedDirection;
+ }
+ }
+
+ /**
+ * Creates a new source viewer. This method is called when creating and
+ * initializing the content areas of the merge viewer (see
+ * {@link #createControls(Composite)}). It is called three
+ * times for each text part of the comparison: ancestor, left, right.
+ * Clients may implement to provide their own type of source viewers. The
+ * viewer is not expected to be configured with a source viewer
+ * configuration.
+ *
+ * @param parent
+ * the parent of the viewer's control
+ * @param textOrientation
+ * style constant bit for text orientation
+ * @return Default implementation returns an instance of
+ * {@link SourceViewer}.
+ * @since 3.5
+ */
+ protected SourceViewer createSourceViewer(Composite parent, int textOrientation) {
+ return new SourceViewer(parent, new CompositeRuler(), textOrientation | SWT.H_SCROLL | SWT.V_SCROLL);
+ }
+
+ /**
+ * Tells whether the given text viewer is backed by an editor.
+ *
+ * @param textViewer the text viewer to check
+ * @return <code>true</code> if the viewer is backed by an editor
+ * @since 3.5
+ */
+ protected boolean isEditorBacked(ITextViewer textViewer) {
+ return false;
+ }
+
+ /**
+ * Returns an editor input for the given source viewer. The method returns
+ * <code>null</code> when no input is available, for example when the input
+ * for the merge viewer has not been set yet.
+ *
+ * @param sourceViewer
+ * the source viewer to get input for
+ * @return input for the given viewer or <code>null</code> when no input is
+ * available
+ *
+ * @since 3.5
+ */
+ protected IEditorInput getEditorInput(ISourceViewer sourceViewer) {
+ if (fLeft != null && sourceViewer == fLeft.getSourceViewer())
+ if (fLeftContributor != null)
+ return fLeftContributor.getDocumentKey();
+ if (fRight != null && sourceViewer == fRight.getSourceViewer())
+ if (fRightContributor != null)
+ return fRightContributor.getDocumentKey();
+ if (fAncestor != null
+ && sourceViewer == fAncestor.getSourceViewer())
+ if (fAncestorContributor != null)
+ return fAncestorContributor.getDocumentKey();
+ return null;
+ }
+
+ /*
+ * Creates and initializes a text part.
+ */
+ private MergeSourceViewer createPart(Composite parent) {
+ final MergeSourceViewer viewer = new MergeSourceViewer(
+ createSourceViewer(parent, getDirection()),
+ getResourceBundle(), getCompareConfiguration().getContainer());
+ final StyledText te= viewer.getSourceViewer().getTextWidget();
+
+ if (!fConfirmSave)
+ viewer.hideSaveAction();
+
+ te.addPaintListener(
+ e -> paint(e, viewer)
+ );
+ te.addKeyListener(
+ new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ handleSelectionChanged(viewer);
+ }
+ }
+ );
+ te.addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent e) {
+ //syncViewport(part);
+ handleSelectionChanged(viewer);
+ }
+ }
+ );
+
+ te.addFocusListener(
+ new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent fe) {
+ setActiveViewer(viewer, true);
+ }
+ @Override
+ public void focusLost(FocusEvent fe) {
+ setActiveViewer(viewer, false);
+ }
+ }
+ );
+
+ viewer.getSourceViewer().addViewportListener(
+ verticalPosition -> syncViewport(viewer)
+ );
+
+ Font font= JFaceResources.getFont(fSymbolicFontName);
+ if (font != null)
+ te.setFont(font);
+
+ if (fBackground != null) // not default
+ te.setBackground(getColor(parent.getDisplay(), fBackground));
+
+ // Add the find action to the popup menu of the viewer
+ contributeFindAction(viewer);
+
+ contributeGotoLineAction(viewer);
+
+ contributeChangeEncodingAction(viewer);
+
+ contributeDiffBackgroundListener(viewer);
+
+ return viewer;
+ }
+
+ private void setActiveViewer(MergeSourceViewer viewer, boolean activate) {
+ connectContributedActions(viewer, activate);
+ if (activate) {
+ fFocusPart= viewer;
+ connectGlobalActions(fFocusPart);
+ } else {
+ connectGlobalActions(null);
+ }
+ }
+
+ private SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) {
+ SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors());
+ support.setCursorLinePainterPreferenceKeys(CURRENT_LINE, CURRENT_LINE_COLOR);
+ fSourceViewerDecorationSupport.add(support);
+ return support;
+ }
+
+ private void contributeFindAction(MergeSourceViewer viewer) {
+ IAction action;
+ IWorkbenchPart wp = getCompareConfiguration().getContainer().getWorkbenchPart();
+ if (wp != null)
+ action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", wp); //$NON-NLS-1$
+ else
+ action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", viewer.getSourceViewer().getControl().getShell(), getFindReplaceTarget()); //$NON-NLS-1$
+ action.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_FIND_AND_REPLACE);
+ viewer.addAction(MergeSourceViewer.FIND_ID, action);
+ }
+
+ private void contributeGotoLineAction(MergeSourceViewer viewer) {
+ IAction action = new GotoLineAction(viewer.getAdapter(ITextEditor.class));
+ action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_GOTO);
+ viewer.addAction(MergeSourceViewer.GOTO_LINE_ID, action);
+ }
+
+ private void contributeChangeEncodingAction(MergeSourceViewer viewer) {
+ IAction action = new ChangeEncodingAction(getTextEditorAdapter());
+ viewer.addAction(MergeSourceViewer.CHANGE_ENCODING_ID, action);
+ }
+
+ private void contributeDiffBackgroundListener(final MergeSourceViewer viewer) {
+ viewer.getSourceViewer().getTextWidget().addLineBackgroundListener(
+ event -> {
+ StyledText textWidget = viewer.getSourceViewer().getTextWidget();
+ if (textWidget != null) {
+
+ int caret = textWidget.getCaretOffset();
+ int length = event.lineText.length();
+
+ if (event.lineOffset <= caret
+ && caret <= event.lineOffset + length) {
+ // current line, do nothing
+ // decorated by CursorLinePainter
+ } else {
+ // find diff for the event line
+ Diff diff = findDiff(viewer, event.lineOffset,
+ event.lineOffset + length);
+ if (diff != null && updateDiffBackground(diff)) {
+ // highlights only the event line, not the
+ // whole diff
+ event.lineBackground = getColor(fComposite
+ .getDisplay(), getFillColor(diff));
+ }
+ }
+ }
+ });
+ }
+
+ private void connectGlobalActions(final MergeSourceViewer part) {
+ if (fHandlerService != null) {
+ if (part != null)
+ part.updateActions();
+ fHandlerService.updatePaneActionHandlers(() -> {
+ for (int i= 0; i < GLOBAL_ACTIONS.length; i++) {
+ IAction action= null;
+ if (part != null) {
+ action= part.getAction(TEXT_ACTIONS[i]);
+ }
+ fHandlerService.setGlobalActionHandler(GLOBAL_ACTIONS[i], action);
+ }
+ });
+ }
+ }
+
+ private void connectContributedActions(final MergeSourceViewer viewer, final boolean connect) {
+ if (fHandlerService != null) {
+ fHandlerService.updatePaneActionHandlers(() -> {
+ if (viewer != null) {
+ setActionsActivated(viewer.getSourceViewer(), connect);
+ if (isEditorBacked(viewer.getSourceViewer()) && connect) {
+ /*
+ * If editor backed, activating contributed actions
+ * might have disconnected actions provided in
+ * CompareEditorContributor => when connecting,
+ * refresh active editor in the contributor, when
+ * disconnecting do nothing. See bug 261229.
+ */
+ IWorkbenchPart part = getCompareConfiguration().getContainer().getWorkbenchPart();
+ if (part instanceof CompareEditor) {
+ ((CompareEditor) part).refreshActionBarsContributor();
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Activates or deactivates actions of the given source viewer.
+ * <p>
+ * The default implementation does nothing, but clients should override to properly react to
+ * viewers switching.
+ * </p>
+ *
+ * @param sourceViewer the source viewer
+ * @param state <code>true</code> if activated
+ * @since 3.5
+ */
+ protected void setActionsActivated(SourceViewer sourceViewer, boolean state) {
+ // default implementation does nothing
+ }
+
+ private IDocument getElementDocument(char type, Object element) {
+ if (element instanceof IDocument) {
+ return (IDocument) element;
+ }
+ ITypedElement te= Utilities.getLeg(type, element);
+ // First check the contributors for the document
+ IDocument document = null;
+ switch (type) {
+ case ANCESTOR_CONTRIBUTOR:
+ document = getDocument(te, fAncestorContributor);
+ break;
+ case LEFT_CONTRIBUTOR:
+ document = getDocument(te, fLeftContributor);
+ break;
+ case RIGHT_CONTRIBUTOR:
+ document = getDocument(te, fRightContributor);
+ break;
+ default:
+ break;
+ }
+ if (document != null)
+ return document;
+ // The document is not associated with the input of the viewer so try to find the document
+ return Utilities.getDocument(type, element, isUsingDefaultContentProvider(), canHaveSharedDocument());
+ }
+
+ private boolean isUsingDefaultContentProvider() {
+ return getContentProvider() instanceof MergeViewerContentProvider;
+ }
+
+ private boolean canHaveSharedDocument() {
+ return getDocumentPartitioning() != null
+ || getDocumentPartitioner() == null;
+ }
+
+ private IDocument getDocument(ITypedElement te, ContributorInfo info) {
+ if (info != null && info.getElement() == te)
+ return info.getDocument();
+ return null;
+ }
+
+ IDocument getDocument(char type, Object input) {
+ IDocument doc= getElementDocument(type, input);
+ if (doc != null)
+ return doc;
+
+ if (input instanceof IDiffElement) {
+ IDiffContainer parent= ((IDiffElement) input).getParent();
+ return getElementDocument(type, parent);
+ }
+ return null;
+ }
+
+ /*
+ * Returns true if the given inputs map to the same documents
+ */
+ boolean sameDoc(char type, Object newInput, Object oldInput) {
+ IDocument newDoc= getDocument(type, newInput);
+ IDocument oldDoc= getDocument(type, oldInput);
+ return newDoc == oldDoc;
+ }
+
+ /**
+ * Overridden to prevent save confirmation if new input is sub document of current input.
+ * @param newInput the new input of this viewer, or <code>null</code> if there is no new input
+ * @param oldInput the old input element, or <code>null</code> if there was previously no input
+ * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog).
+ * @since 2.0
+ */
+ @Override
+ protected boolean doSave(Object newInput, Object oldInput) {
+ // TODO: Would be good if this could be restated in terms of Saveables and moved up
+ if (oldInput != null && newInput != null) {
+ // check whether underlying documents have changed.
+ if (sameDoc(ANCESTOR_CONTRIBUTOR, newInput, oldInput) &&
+ sameDoc(LEFT_CONTRIBUTOR, newInput, oldInput) &&
+ sameDoc(RIGHT_CONTRIBUTOR, newInput, oldInput)) {
+ if (DEBUG) System.out.println("----- Same docs !!!!"); //$NON-NLS-1$
+ return false;
+ }
+ }
+
+ if (DEBUG) System.out.println("***** New docs !!!!"); //$NON-NLS-1$
+
+ removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, oldInput);
+ removeFromDocumentManager(LEFT_CONTRIBUTOR, oldInput);
+ removeFromDocumentManager(RIGHT_CONTRIBUTOR, oldInput);
+
+ if (DEBUG)
+ DocumentManager.dump();
+
+ return super.doSave(newInput, oldInput);
+ }
+
+ private void removeFromDocumentManager(char leg, Object oldInput) {
+ IDocument document= getDocument(leg, oldInput);
+ if (document != null)
+ DocumentManager.remove(document);
+ }
+
+ private ITypedElement getParent(char type) {
+ Object input= getInput();
+ if (input instanceof IDiffElement) {
+ IDiffContainer parent= ((IDiffElement) input).getParent();
+ return Utilities.getLeg(type, parent);
+ }
+ return null;
+ }
+
+ /*
+ * Initializes the text viewers of the three content areas with the given input objects.
+ * Subclasses may extend.
+ */
+ @Override
+ protected void updateContent(Object ancestor, Object left, Object right) {
+ boolean emptyInput= (ancestor == null && left == null && right == null);
+
+ Object input= getInput();
+
+ configureCompareFilterActions(input, ancestor, left, right);
+
+ Position leftRange= null;
+ Position rightRange= null;
+
+ // if one side is empty use container
+ if (FIX_47640 && !emptyInput && (left == null || right == null)) {
+ if (input instanceof IDiffElement) {
+ IDiffContainer parent= ((IDiffElement) input).getParent();
+ if (parent instanceof ICompareInput) {
+ ICompareInput ci= (ICompareInput) parent;
+
+ if (ci.getAncestor() instanceof IDocumentRange
+ || ci.getLeft() instanceof IDocumentRange
+ || ci.getRight() instanceof IDocumentRange) {
+ if (left instanceof IDocumentRange)
+ leftRange= ((IDocumentRange) left).getRange();
+ if (right instanceof IDocumentRange)
+ rightRange= ((IDocumentRange) right).getRange();
+
+ ancestor= ci.getAncestor();
+ left= getCompareConfiguration().isMirrored() ? ci.getRight() : ci.getLeft();
+ right= getCompareConfiguration().isMirrored() ? ci.getLeft() : ci.getRight();
+ }
+ }
+ }
+ }
+
+ fHighlightRanges= left != null && right != null;
+
+ resetDiffs();
+ fHasErrors= false; // start with no errors
+
+ IMergeViewerContentProvider cp= getMergeContentProvider();
+
+ if (cp instanceof MergeViewerContentProvider) {
+ MergeViewerContentProvider mcp= (MergeViewerContentProvider) cp;
+ mcp.setAncestorError(null);
+ mcp.setLeftError(null);
+ mcp.setRightError(null);
+ }
+
+ // Record current contributors so we disconnect after creating the new ones.
+ // This is done in case the old and new use the same document.
+ ContributorInfo oldLeftContributor = fLeftContributor;
+ ContributorInfo oldRightContributor = fRightContributor;
+ ContributorInfo oldAncestorContributor = fAncestorContributor;
+
+ // Create the new contributor
+ fLeftContributor = createLegInfoFor(left, LEFT_CONTRIBUTOR);
+ fRightContributor = createLegInfoFor(right, RIGHT_CONTRIBUTOR);
+ fAncestorContributor = createLegInfoFor(ancestor, ANCESTOR_CONTRIBUTOR);
+
+ fLeftContributor.transferContributorStateFrom(oldLeftContributor);
+ fRightContributor.transferContributorStateFrom(oldRightContributor);
+ fAncestorContributor.transferContributorStateFrom(oldAncestorContributor);
+
+ // Now disconnect the old ones
+ disconnect(oldLeftContributor);
+ disconnect(oldRightContributor);
+ disconnect(oldAncestorContributor);
+
+ // Get encodings from streams. If an encoding is null, abide by the other one
+ // Defaults to workbench encoding only if both encodings are null
+ fLeftContributor.setEncodingIfAbsent(fRightContributor);
+ fRightContributor.setEncodingIfAbsent(fLeftContributor);
+ fAncestorContributor.setEncodingIfAbsent(fLeftContributor);
+
+ if (!isConfigured) {
+ configureSourceViewer(fAncestor.getSourceViewer(), false, null);
+ configureSourceViewer(fLeft.getSourceViewer(), isLeftEditable() && cp.isLeftEditable(input), fLeftContributor);
+ configureSourceViewer(fRight.getSourceViewer(), isRightEditable() && cp.isRightEditable(input), fRightContributor);
+ isConfigured = true; // configure once
+ }
+
+ // set new documents
+ fLeftContributor.setDocument(fLeft, isLeftEditable() && cp.isLeftEditable(input));
+ fLeftLineCount= fLeft.getLineCount();
+
+ fRightContributor.setDocument(fRight, isRightEditable() && cp.isRightEditable(input));
+ fRightLineCount= fRight.getLineCount();
+
+ fAncestorContributor.setDocument(fAncestor, false);
+
+ setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING));
+
+ update(false);
+
+ if (!fHasErrors && !emptyInput && !fComposite.isDisposed()) {
+ if (isRefreshing()) {
+ fLeftContributor.updateSelection(fLeft, !fSynchronizedScrolling);
+ fRightContributor.updateSelection(fRight, !fSynchronizedScrolling);
+ fAncestorContributor.updateSelection(fAncestor, !fSynchronizedScrolling);
+ if (fSynchronizedScrolling && fSynchronziedScrollPosition != -1) {
+ synchronizedScrollVertical(fSynchronziedScrollPosition);
+ }
+ } else {
+ if (isPatchHunk()) {
+ if (right != null && Adapters.adapt(right, IHunk.class) != null)
+ fLeft.getSourceViewer().setTopIndex(getHunkStart());
+ else
+ fRight.getSourceViewer().setTopIndex(getHunkStart());
+ } else {
+ Diff selectDiff= null;
+ if (FIX_47640) {
+ if (leftRange != null)
+ selectDiff= fMerger.findDiff(LEFT_CONTRIBUTOR, leftRange);
+ else if (rightRange != null)
+ selectDiff= fMerger.findDiff(RIGHT_CONTRIBUTOR, rightRange);
+ }
+ if (selectDiff != null)
+ setCurrentDiff(selectDiff, true);
+ else
+ selectFirstDiff(true);
+ }
+ }
+ }
+
+ }
+
+ private void configureSourceViewer(SourceViewer sourceViewer, boolean editable, ContributorInfo contributor) {
+ setEditable(sourceViewer, editable);
+ configureTextViewer(sourceViewer);
+ if (editable && contributor != null) {
+ IDocument document = sourceViewer.getDocument();
+ if (document != null)
+ contributor.connectPositionUpdater(document);
+ }
+ if (!isCursorLinePainterInstalled(sourceViewer))
+ getSourceViewerDecorationSupport(sourceViewer).install(fPreferenceStore);
+ }
+
+ private boolean isCursorLinePainterInstalled(SourceViewer viewer) {
+ Listener[] listeners = viewer.getTextWidget().getListeners(3001/*StyledText.LineGetBackground*/);
+ for (int i = 0; i < listeners.length; i++) {
+ if (listeners[i] instanceof TypedListener) {
+ TypedListener listener = (TypedListener) listeners[i];
+ if (listener.getEventListener() instanceof CursorLinePainter)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the editable state of the given source viewer.
+ *
+ * @param sourceViewer
+ * the source viewer
+ * @param state
+ * the state
+ * @since 3.5
+ */
+ protected void setEditable(ISourceViewer sourceViewer, boolean state) {
+ sourceViewer.setEditable(state);
+ }
+
+ private boolean isRefreshing() {
+ return isRefreshing > 0;
+ }
+
+ private ContributorInfo createLegInfoFor(Object element, char leg) {
+ return new ContributorInfo(this, element, leg);
+ }
+
+ private boolean updateDiffBackground(Diff diff) {
+
+ if (!fHighlightRanges)
+ return false;
+
+ if (diff == null || diff.isToken())
+ return false;
+
+ if (fShowCurrentOnly && !isCurrentDiff(diff))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Called whenever one of the documents changes.
+ * Sets the dirty state of this viewer and updates the lines.
+ * Implements IDocumentListener.
+ */
+ private void documentChanged(DocumentEvent e, boolean dirty) {
+ final IDocument doc= e.getDocument();
+
+ if (doc == fLeft.getSourceViewer().getDocument()) {
+ setLeftDirty(dirty);
+ } else if (doc == fRight.getSourceViewer().getDocument()) {
+ setRightDirty(dirty);
+ }
+ if (!isLeftDirty() && !isRightDirty()) {
+ fRedoDiff = false;
+ final Diff oldDiff = getLastDiff();
+ new UIJob(CompareMessages.DocumentMerger_0) {
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ if (!getControl().isDisposed()) {
+ doDiff();
+ if (!getControl().isDisposed()) {
+ Diff newDiff = findNewDiff(oldDiff);
+ if (newDiff != null) {
+ updateStatus(newDiff);
+ setCurrentDiff(newDiff, true);
+ }
+ invalidateLines();
+ updateLines(doc);
+ }
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ } else {
+ updateLines(doc);
+ }
+ }
+
+
+ private void saveDiff() {
+ fSavedDiff = fCurrentDiff;
+ }
+
+ private Diff getLastDiff() {
+ if (fCurrentDiff != null) {
+ return fCurrentDiff;
+ }
+ return fSavedDiff;
+ }
+
+
+ private Diff findNewDiff(Diff oldDiff) {
+ if (oldDiff == null)
+ return null;
+ Diff newDiff = findNewDiff(oldDiff, LEFT_CONTRIBUTOR);
+ if (newDiff == null) {
+ newDiff = findNewDiff(oldDiff, RIGHT_CONTRIBUTOR);
+ }
+ return newDiff;
+ }
+
+ private Diff findNewDiff(Diff oldDiff, char type) {
+ int offset = oldDiff.getPosition(type).offset;
+ int length = oldDiff.getPosition(type).length;
+
+ // DocumentMerger.findDiff method doesn't really work well with 0-length
+ // diffs
+ if (length == 0) {
+ if (offset > 0)
+ offset--;
+ length = 1;
+ }
+ return fMerger.findDiff(type, offset, offset + length);
+ }
+
+ /*
+ * This method is called if a range of text on one side is copied into an empty sub-document
+ * on the other side. The method returns the position where the sub-document is placed into the base document.
+ * This default implementation determines the position by using the text range differencer.
+ * However this position is not always optimal for specific types of text.
+ * So subclasses (which are aware of the type of text they are dealing with)
+ * may override this method to find a better position where to insert a newly added
+ * piece of text.
+ * @param type the side for which the insertion position should be determined: 'A' for ancestor, 'L' for left hand side, 'R' for right hand side.
+ * @param input the current input object of this viewer
+ * @since 2.0
+ */
+ protected int findInsertionPosition(char type, ICompareInput input) {
+
+ ITypedElement other= null;
+ char otherType= 0;
+
+ switch (type) {
+ case ANCESTOR_CONTRIBUTOR:
+ other= input.getLeft();
+ otherType= LEFT_CONTRIBUTOR;
+ if (other == null) {
+ other= input.getRight();
+ otherType= RIGHT_CONTRIBUTOR;
+ }
+ break;
+ case LEFT_CONTRIBUTOR:
+ other= input.getRight();
+ otherType= RIGHT_CONTRIBUTOR;
+ if (other == null) {
+ other= input.getAncestor();
+ otherType= ANCESTOR_CONTRIBUTOR;
+ }
+ break;
+ case RIGHT_CONTRIBUTOR:
+ other= input.getLeft();
+ otherType= LEFT_CONTRIBUTOR;
+ if (other == null) {
+ other= input.getAncestor();
+ otherType= ANCESTOR_CONTRIBUTOR;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (other instanceof IDocumentRange) {
+ IDocumentRange dr= (IDocumentRange) other;
+ Position p= dr.getRange();
+ Diff diff= findDiff(otherType, p.offset);
+ return fMerger.findInsertionPoint(diff, type);
+ }
+ return 0;
+ }
+
+ private void setError(char type, String message) {
+ IMergeViewerContentProvider cp= getMergeContentProvider();
+ if (cp instanceof MergeViewerContentProvider) {
+ MergeViewerContentProvider mcp= (MergeViewerContentProvider) cp;
+ switch (type) {
+ case ANCESTOR_CONTRIBUTOR:
+ mcp.setAncestorError(message);
+ break;
+ case LEFT_CONTRIBUTOR:
+ mcp.setLeftError(message);
+ break;
+ case RIGHT_CONTRIBUTOR:
+ mcp.setRightError(message);
+ break;
+ default:
+ break;
+ }
+ }
+ fHasErrors= true;
+ }
+
+ private void updateDirtyState(IEditorInput key,
+ IDocumentProvider documentProvider, char type) {
+ boolean dirty = documentProvider.canSaveDocument(key);
+ boolean oldLeftDirty = isLeftDirty();
+ boolean oldRightDirty = isRightDirty();
+ if (type == LEFT_CONTRIBUTOR)
+ setLeftDirty(dirty);
+ else if (type == RIGHT_CONTRIBUTOR)
+ setRightDirty(dirty);
+ if ((oldLeftDirty && !isLeftDirty())
+ || (oldRightDirty && !isRightDirty())) {
+ /*
+ * Dirty state has changed from true to false, combined dirty state
+ * is false. _In most cases_ this means that save has taken place
+ * outside compare editor. Ask to redo diff calculation when the
+ * editor gets focus.
+ *
+ * However, undoing all the changes made in another editor would
+ * result in asking for redo diff as well. In this case, we set the
+ * flag back to false, see
+ * TextMergeViewer.documentChanged(DocumentEvent, boolean)
+ */
+ fRedoDiff = true;
+ }
+ }
+
+ private Position getNewRange(char type, Object input) {
+ switch (type) {
+ case ANCESTOR_CONTRIBUTOR:
+ return fNewAncestorRanges.get(input);
+ case LEFT_CONTRIBUTOR:
+ return fNewLeftRanges.get(input);
+ case RIGHT_CONTRIBUTOR:
+ return fNewRightRanges.get(input);
+ default:
+ return null;
+ }
+ }
+
+ private void addNewRange(char type, Object input, Position range) {
+ switch (type) {
+ case ANCESTOR_CONTRIBUTOR:
+ fNewAncestorRanges.put(input, range);
+ break;
+ case LEFT_CONTRIBUTOR:
+ fNewLeftRanges.put(input, range);
+ break;
+ case RIGHT_CONTRIBUTOR:
+ fNewRightRanges.put(input, range);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Returns the contents of the underlying document as an array of bytes using the current workbench encoding.
+ *
+ * @param left if <code>true</code> the contents of the left side is returned; otherwise the right side
+ * @return the contents of the left or right document or null
+ */
+ @Override
+ protected byte[] getContents(boolean left) {
+ MergeSourceViewer v= left ? fLeft : fRight;
+ if (v != null) {
+ IDocument d= v.getSourceViewer().getDocument();
+ if (d != null) {
+ String contents= d.get();
+ if (contents != null) {
+ byte[] bytes;
+ try {
+ bytes= contents.getBytes(left ? fLeftContributor.internalGetEncoding() : fRightContributor.internalGetEncoding());
+ } catch(UnsupportedEncodingException ex) {
+ // use default encoding
+ bytes= contents.getBytes();
+ }
+ return bytes;
+ }
+ }
+ }
+ return null;
+ }
+
+ private IRegion normalizeDocumentRegion(IDocument doc, IRegion region) {
+
+ if (region == null || doc == null)
+ return region;
+
+ int maxLength= doc.getLength();
+
+ int start= region.getOffset();
+ if (start < 0)
+ start= 0;
+ else if (start > maxLength)
+ start= maxLength;
+
+ int length= region.getLength();
+ if (length < 0)
+ length= 0;
+ else if (start + length > maxLength)
+ length= maxLength - start;
+
+ return new Region(start, length);
+ }
+
+ @Override
+ protected final void handleResizeAncestor(int x, int y, int width, int height) {
+ if (width > 0) {
+ Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0);
+ int scrollbarHeight= trim.height;
+ if (Utilities.okToUse(fAncestorCanvas))
+ fAncestorCanvas.setVisible(true);
+ if (fAncestor.isControlOkToUse())
+ fAncestor.getSourceViewer().getTextWidget().setVisible(true);
+
+ if (fAncestorCanvas != null) {
+ fAncestorCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight);
+ x+= fMarginWidth;
+ width-= fMarginWidth;
+ }
+ fAncestor.setBounds(x, y, width, height);
+ } else {
+ if (Utilities.okToUse(fAncestorCanvas))
+ fAncestorCanvas.setVisible(false);
+ if (fAncestor.isControlOkToUse()) {
+ StyledText t= fAncestor.getSourceViewer().getTextWidget();
+ t.setVisible(false);
+ fAncestor.setBounds(0, 0, 0, 0);
+ if (fFocusPart == fAncestor) {
+ fFocusPart= fLeft;
+ fFocusPart.getSourceViewer().getTextWidget().setFocus();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected final void handleResizeLeftRight(int x, int y, int width1, int centerWidth, int width2, int height) {
+ if (fBirdsEyeCanvas != null)
+ width2-= BIRDS_EYE_VIEW_WIDTH;
+
+ Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0);
+ int scrollbarHeight= trim.height + trim.x;
+
+ Composite composite= (Composite) getControl();
+
+ int leftTextWidth= width1;
+ if (fLeftCanvas != null) {
+ fLeftCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight);
+ x+= fMarginWidth;
+ leftTextWidth-= fMarginWidth;
+ }
+
+ fLeft.setBounds(x, y, leftTextWidth, height);
+ x+= leftTextWidth;
+
+ if (fCenter == null || fCenter.isDisposed())
+ fCenter= createCenterControl(composite);
+ fCenter.setBounds(x, y, centerWidth, height-scrollbarHeight);
+ x+= centerWidth;
+
+ if (!fSynchronizedScrolling) { // canvas is to the left of text
+ if (fRightCanvas != null) {
+ fRightCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight);
+ fRightCanvas.redraw();
+ x+= fMarginWidth;
+ }
+ // we draw the canvas to the left of the text widget
+ }
+
+ int scrollbarWidth= 0;
+ if (fSynchronizedScrolling && fScrollCanvas != null) {
+ trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0);
+ // one pixel was cut off
+ scrollbarWidth= trim.width + 2*trim.x+1;
+ }
+ int rightTextWidth= width2-scrollbarWidth;
+ if (fRightCanvas != null)
+ rightTextWidth-= fMarginWidth;
+ fRight.setBounds(x, y, rightTextWidth, height);
+ x+= rightTextWidth;
+
+ if (fSynchronizedScrolling) {
+ if (fRightCanvas != null) { // canvas is to the right of the text
+ fRightCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight);
+ x+= fMarginWidth;
+ }
+ if (fScrollCanvas != null)
+ fScrollCanvas.setBounds(x, y, scrollbarWidth, height-scrollbarHeight);
+ }
+
+ if (fBirdsEyeCanvas != null) {
+ int verticalScrollbarButtonHeight= scrollbarWidth;
+ int horizontalScrollbarButtonHeight= scrollbarHeight;
+ if (fIsMac) {
+ verticalScrollbarButtonHeight+= 2;
+ horizontalScrollbarButtonHeight= 18;
+ }
+ if (fSummaryHeader != null)
+ fSummaryHeader.setBounds(x+scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH, verticalScrollbarButtonHeight);
+ y+= verticalScrollbarButtonHeight;
+ fBirdsEyeCanvas.setBounds(x+scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH, height-(2*verticalScrollbarButtonHeight+horizontalScrollbarButtonHeight));
+ }
+
+ // doesn't work since TextEditors don't have their correct size yet.
+ updateVScrollBar();
+ refreshBirdsEyeView();
+ }
+
+ /*
+ * Track selection changes to update the current Diff.
+ */
+ private void handleSelectionChanged(MergeSourceViewer tw) {
+ Point p= tw.getSourceViewer().getSelectedRange();
+ Diff d= findDiff(tw, p.x, p.x+p.y);
+ updateStatus(d);
+ setCurrentDiff(d, false); // don't select or reveal
+ }
+
+ private static IRegion toRegion(Position position) {
+ if (position != null)
+ return new Region(position.getOffset(), position.getLength());
+ return null;
+ }
+
+ //---- the differencing
+
+ /**
+ * 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() {
+ IDocument lDoc= fLeft.getSourceViewer().getDocument();
+ IDocument rDoc= fRight.getSourceViewer().getDocument();
+ if (lDoc == null || rDoc == null)
+ return;
+ fAncestor.resetLineBackground();
+ fLeft.resetLineBackground();
+ fRight.resetLineBackground();
+ saveDiff();
+ fCurrentDiff= null;
+ try {
+ fMerger.doDiff();
+ } catch (CoreException e) {
+ CompareUIPlugin.log(e.getStatus());
+ String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
+ String msg= Utilities.getString(getResourceBundle(), "tooComplexError.message"); //$NON-NLS-1$
+ MessageDialog.openError(fComposite.getShell(), title, msg);
+ }
+
+ 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 msg= Utilities.getString(getResourceBundle(), "tooComplexError.message"); //$NON-NLS-1$
+ MessageDialog.openError(fComposite.getShell(), title, msg);
+ return null;
+ }
+ }
+
+ private void resetPositions(IDocument doc) {
+ if (doc == null)
+ return;
+ try {
+ doc.removePositionCategory(DIFF_RANGE_CATEGORY);
+ } catch (BadPositionCategoryException e) {
+ // Ignore
+ }
+ doc.addPositionCategory(DIFF_RANGE_CATEGORY);
+ }
+
+ //---- update UI stuff
+
+ private void updateControls() {
+ if (getControl().isDisposed())
+ return;
+
+ boolean leftToRight= false;
+ boolean rightToLeft= false;
+
+ updateStatus(fCurrentDiff);
+ updateResolveStatus();
+
+ if (fCurrentDiff != null) {
+ IMergeViewerContentProvider cp= getMergeContentProvider();
+ if (cp != null) {
+ if (!isPatchHunk()) {
+ rightToLeft= cp.isLeftEditable(getInput());
+ leftToRight= cp.isRightEditable(getInput());
+ }
+ }
+ }
+
+ if (fDirectionLabel != null) {
+ if (fHighlightRanges && fCurrentDiff != null && isThreeWay() && !isIgnoreAncestor()) {
+ fDirectionLabel.setImage(fCurrentDiff.getImage());
+ } else {
+ fDirectionLabel.setImage(null);
+ }
+ }
+
+ if (fCopyDiffLeftToRightItem != null)
+ fCopyDiffLeftToRightItem.getAction().setEnabled(leftToRight);
+ if (fCopyDiffRightToLeftItem != null)
+ fCopyDiffRightToLeftItem.getAction().setEnabled(rightToLeft);
+
+ if (fNextDiff != null) {
+ IAction a = fNextDiff.getAction();
+ a.setEnabled(isNavigationButtonEnabled(true, false));
+ }
+ if (fPreviousDiff != null) {
+ IAction a = fPreviousDiff.getAction();
+ a.setEnabled(isNavigationButtonEnabled(false, false));
+ }
+ if (fNextChange != null) {
+ IAction a = fNextChange.getAction();
+ a.setEnabled(isNavigationButtonEnabled(true, true));
+ }
+ if (fPreviousChange != null) {
+ IAction a = fPreviousChange.getAction();
+ a.setEnabled(isNavigationButtonEnabled(false, true));
+ }
+ }
+
+ private boolean isNavigationButtonEnabled(boolean down, boolean deep) {
+ String value = fPreferenceStore
+ .getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
+ if (value.equals(ICompareUIConstants.PREF_VALUE_DO_NOTHING)) {
+ return getNextVisibleDiff(down, deep) != null;
+ } else if (value.equals(ICompareUIConstants.PREF_VALUE_LOOP)) {
+ return isNavigationPossible();
+ } else if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) {
+ return getNextVisibleDiff(down, deep) != null || hasNextElement(down);
+ } else if (value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) {
+ return isNavigationPossible() || hasNextElement(true);
+ }
+ Assert.isTrue(false);
+ return false;
+ }
+
+ private void updateResolveStatus() {
+
+ RGB rgb= null;
+
+ if (showResolveUI()) {
+ // we only show red or green if there is at least one incoming or conflicting change
+ int unresolvedIncoming= 0;
+ int unresolvedConflicting= 0;
+
+ if (fMerger.hasChanges()) {
+ for (Iterator<?> iterator = fMerger.changesIterator(); iterator .hasNext();) {
+ Diff d = (Diff) iterator.next();
+ if (!d.isResolved()) {
+ if (d.getKind() == RangeDifference.CONFLICT) {
+ unresolvedConflicting++;
+ break; // we can stop here because a conflict has the maximum priority
+ }
+ unresolvedIncoming++;
+ }
+ }
+ }
+
+ if (unresolvedConflicting > 0)
+ rgb= SELECTED_CONFLICT;
+ else if (unresolvedIncoming > 0)
+ rgb= SELECTED_INCOMING;
+ else
+ rgb= RESOLVED;
+ }
+
+ if (fHeaderPainter.setColor(rgb))
+ fSummaryHeader.redraw();
+ }
+
+ private void updateStatus(Diff diff) {
+
+ String diffDescription;
+
+ if (diff == null) {
+ diffDescription= CompareMessages.TextMergeViewer_diffDescription_noDiff_format;
+ } else {
+
+ 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,
+ getDiffType(diff), // 0: diff type
+ getDiffNumber(diff), // 1: diff number
+ getDiffRange(fLeft, diff.getPosition(LEFT_CONTRIBUTOR)), // 2: left start line
+ getDiffRange(fRight, diff.getPosition(RIGHT_CONTRIBUTOR)) // 3: left end line
+ );
+ }
+
+ String format= CompareMessages.TextMergeViewer_statusLine_format;
+ String s= MessageFormat.format(format,
+ getCursorPosition(fLeft), // 0: left column
+ getCursorPosition(fRight), // 1: right column
+ diffDescription // 2: diff description
+ );
+
+ getCompareConfiguration().getContainer().setStatusMessage(s);
+ }
+
+ private String getDiffType(Diff diff) {
+ String s= ""; //$NON-NLS-1$
+ switch(diff.getKind()) {
+ case RangeDifference.LEFT:
+ s= getCompareConfiguration().isMirrored() ?
+ CompareMessages.TextMergeViewer_direction_incoming :
+ CompareMessages.TextMergeViewer_direction_outgoing;
+ break;
+ case RangeDifference.RIGHT:
+ s= getCompareConfiguration().isMirrored() ?
+ CompareMessages.TextMergeViewer_direction_outgoing :
+ CompareMessages.TextMergeViewer_direction_incoming;
+ break;
+ case RangeDifference.CONFLICT:
+ s= CompareMessages.TextMergeViewer_direction_conflicting;
+ break;
+ default:
+ break;
+ }
+ String format= CompareMessages.TextMergeViewer_diffType_format;
+ return MessageFormat.format(format, s, diff.changeType());
+ }
+
+ private String getDiffNumber(Diff diff) {
+ // find the diff's number
+ int diffNumber= 0;
+ if (fMerger.hasChanges()) {
+ for (Iterator<?> iterator = fMerger.changesIterator(); iterator.hasNext();) {
+ Diff d = (Diff) iterator.next();
+ diffNumber++;
+ if (d == diff)
+ break;
+ }
+ }
+ return Integer.toString(diffNumber);
+ }
+
+ private String getDiffRange(MergeSourceViewer v, Position pos) {
+ Point p= v.getLineRange(pos, new Point(0, 0));
+ int startLine= p.x+1;
+ int endLine= p.x+p.y;
+
+ String format;
+ if (endLine < startLine)
+ format= CompareMessages.TextMergeViewer_beforeLine_format;
+ else
+ format= CompareMessages.TextMergeViewer_range_format;
+ return MessageFormat.format(format, Integer.toString(startLine), Integer.toString(endLine));
+ }
+
+ /*
+ * Returns a description of the cursor position.
+ *
+ * @return a description of the cursor position
+ */
+ private String getCursorPosition(MergeSourceViewer v) {
+ if (v != null) {
+ StyledText styledText= v.getSourceViewer().getTextWidget();
+
+ IDocument document= v.getSourceViewer().getDocument();
+ if (document != null) {
+ int offset= v.getSourceViewer().getVisibleRegion().getOffset();
+ int caret= offset + styledText.getCaretOffset();
+
+ try {
+ int line=document.getLineOfOffset(caret);
+
+ int lineOffset= document.getLineOffset(line);
+ int occurrences= 0;
+ for (int i= lineOffset; i < caret; i++)
+ if ('\t' == document.getChar(i))
+ ++ occurrences;
+
+ int tabWidth= styledText.getTabs();
+ int column= caret - lineOffset + (tabWidth -1) * occurrences;
+
+ String format= CompareMessages.TextMergeViewer_cursorPosition_format;
+ return MessageFormat.format(format,
+ Integer.toString(line + 1), Integer.toString(column + 1) );
+ } catch (BadLocationException x) {
+ // silently ignored
+ }
+ }
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ protected void updateHeader() {
+ super.updateHeader();
+
+ updateControls();
+ }
+
+ /*
+ * Creates the two items for copying a difference range from one side to the other
+ * and adds them to the given toolbar manager.
+ */
+ @Override
+ protected void createToolItems(ToolBarManager tbm) {
+ fHandlerService= CompareHandlerService.createFor(getCompareConfiguration().getContainer(), fLeft.getSourceViewer().getControl().getShell());
+
+ final String ignoreAncestorActionKey= "action.IgnoreAncestor."; //$NON-NLS-1$
+ Action ignoreAncestorAction= new Action() {
+ @Override
+ public void run() {
+ // First make sure the ancestor is hidden
+ if (!isIgnoreAncestor())
+ getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, Boolean.FALSE);
+ // Then set the property to ignore the ancestor
+ getCompareConfiguration().setProperty(ICompareUIConstants.PROP_IGNORE_ANCESTOR, Boolean.valueOf(!isIgnoreAncestor()));
+ Utilities.initToggleAction(this, getResourceBundle(), ignoreAncestorActionKey, isIgnoreAncestor());
+ }
+ };
+ ignoreAncestorAction.setChecked(isIgnoreAncestor());
+ Utilities.initAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey);
+ Utilities.initToggleAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey, isIgnoreAncestor());
+
+ fIgnoreAncestorItem= new ActionContributionItem(ignoreAncestorAction);
+ fIgnoreAncestorItem.setVisible(false);
+ tbm.appendToGroup("modes", fIgnoreAncestorItem); //$NON-NLS-1$
+
+ tbm.add(new Separator());
+
+ Action a= new Action() {
+ @Override
+ public void run() {
+ if (navigate(true, false, false)) {
+ endOfDocumentReached(true);
+ }
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.NextDiff."); //$NON-NLS-1$
+ fNextDiff= new ActionContributionItem(a);
+ tbm.appendToGroup("navigation", fNextDiff); //$NON-NLS-1$
+ // Don't register this action since it is probably registered by the container
+
+ a= new Action() {
+ @Override
+ public void run() {
+ if (navigate(false, false, false)) {
+ endOfDocumentReached(false);
+ }
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.PrevDiff."); //$NON-NLS-1$
+ fPreviousDiff= new ActionContributionItem(a);
+ tbm.appendToGroup("navigation", fPreviousDiff); //$NON-NLS-1$
+ // Don't register this action since it is probably registered by the container
+
+ a= new Action() {
+ @Override
+ public void run() {
+ if (navigate(true, false, true)) {
+ endOfDocumentReached(true);
+ }
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.NextChange."); //$NON-NLS-1$
+ fNextChange= new ActionContributionItem(a);
+ tbm.appendToGroup("navigation", fNextChange); //$NON-NLS-1$
+ fHandlerService.registerAction(a, "org.eclipse.compare.selectNextChange"); //$NON-NLS-1$
+
+ a= new Action() {
+ @Override
+ public void run() {
+ if (navigate(false, false, true)) {
+ endOfDocumentReached(false);
+ }
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.PrevChange."); //$NON-NLS-1$
+ fPreviousChange= new ActionContributionItem(a);
+ tbm.appendToGroup("navigation", fPreviousChange); //$NON-NLS-1$
+ fHandlerService.registerAction(a, "org.eclipse.compare.selectPreviousChange"); //$NON-NLS-1$
+
+ a= new Action() {
+ @Override
+ public void run() {
+ copyDiffLeftToRight();
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
+ fCopyDiffLeftToRightItem= new ActionContributionItem(a);
+ fCopyDiffLeftToRightItem.setVisible(isRightEditable());
+ tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
+ fHandlerService.registerAction(a, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$
+
+ a= new Action() {
+ @Override
+ public void run() {
+ copyDiffRightToLeft();
+ }
+ };
+ Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
+ fCopyDiffRightToLeftItem= new ActionContributionItem(a);
+ fCopyDiffRightToLeftItem.setVisible(isLeftEditable());
+ tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
+ fHandlerService.registerAction(a, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$
+
+ fIgnoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(getResourceBundle(), getCompareConfiguration());
+ fIgnoreWhitespace.setActionDefinitionId(ICompareUIConstants.COMMAND_IGNORE_WHITESPACE);
+ fLeft.addTextAction(fIgnoreWhitespace);
+ fRight.addTextAction(fIgnoreWhitespace);
+ fAncestor.addTextAction(fIgnoreWhitespace);
+ fHandlerService.registerAction(fIgnoreWhitespace, fIgnoreWhitespace.getActionDefinitionId());
+
+ boolean needsLeftPainter= !isEditorBacked(fLeft.getSourceViewer());
+ boolean needsRightPainter= !isEditorBacked(fLeft.getSourceViewer());
+ boolean needsAncestorPainter= !isEditorBacked(fAncestor.getSourceViewer());
+ showWhitespaceAction = new ShowWhitespaceAction(
+ new MergeSourceViewer[] {fLeft, fRight, fAncestor},
+ new boolean[] {needsLeftPainter, needsRightPainter, needsAncestorPainter });
+ fHandlerService.registerAction(showWhitespaceAction, ITextEditorActionDefinitionIds.SHOW_WHITESPACE_CHARACTERS);
+
+ toggleLineNumbersAction = new LineNumberRulerToggleAction(CompareMessages.TextMergeViewer_16,
+ new MergeSourceViewer[] { fLeft, fRight, fAncestor },
+ AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER);
+ fHandlerService.registerAction(toggleLineNumbersAction, ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE);
+ }
+
+ private void configureCompareFilterActions(Object input, Object ancestor,
+ Object left, Object right) {
+ if (getCompareConfiguration() != null) {
+ CompareFilterDescriptor[] compareFilterDescriptors =
+ CompareUIPlugin.getDefault().findCompareFilters(input);
+
+ Object current = getCompareConfiguration().getProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTER_ACTIONS);
+ boolean currentFiltersMatch = false;
+ if (current != null
+ && current instanceof List
+ && ((List<?>) current).size() == compareFilterDescriptors.length) {
+ currentFiltersMatch = true;
+ @SuppressWarnings("unchecked")
+ List<ChangeCompareFilterPropertyAction> currentFilterActions =
+ (List<ChangeCompareFilterPropertyAction>) current;
+ for (int i = 0; i < compareFilterDescriptors.length; i++) {
+ boolean match = false;
+ for (int j = 0; j < currentFilterActions.size(); j++) {
+ if (compareFilterDescriptors[i]
+ .getFilterId()
+ .equals(currentFilterActions.get(j).getFilterId())) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ currentFiltersMatch = false;
+ break;
+ }
+ }
+ }
+
+ if (!currentFiltersMatch) {
+ getCompareConfiguration()
+ .setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTERS_INITIALIZING,
+ Boolean.TRUE);
+ disposeCompareFilterActions(true);
+ fCompareFilterActions.clear();
+ for (int i = 0; i < compareFilterDescriptors.length; i++) {
+ ChangeCompareFilterPropertyAction compareFilterAction = new ChangeCompareFilterPropertyAction(
+ compareFilterDescriptors[i],
+ getCompareConfiguration());
+ compareFilterAction.setInput(input, ancestor, left, right);
+ fCompareFilterActions.add(compareFilterAction);
+ fLeft.addTextAction(compareFilterAction);
+ fRight.addTextAction(compareFilterAction);
+ fAncestor.addTextAction(compareFilterAction);
+
+ if (getCompareConfiguration().getContainer()
+ .getActionBars() != null) {
+ getCompareConfiguration()
+ .getContainer()
+ .getActionBars()
+ .getToolBarManager()
+ .appendToGroup(
+ CompareEditorContributor.FILTER_SEPARATOR,
+ compareFilterAction);
+ if (compareFilterAction.getActionDefinitionId() != null)
+ getCompareConfiguration()
+ .getContainer()
+ .getActionBars()
+ .setGlobalActionHandler(
+ compareFilterAction
+ .getActionDefinitionId(),
+ compareFilterAction);
+ }
+ }
+ if (!fCompareFilterActions.isEmpty()
+ && getCompareConfiguration().getContainer()
+ .getActionBars() != null) {
+ getCompareConfiguration().getContainer().getActionBars()
+ .getToolBarManager().markDirty();
+ getCompareConfiguration().getContainer().getActionBars()
+ .getToolBarManager().update(true);
+ getCompareConfiguration().getContainer().getActionBars()
+ .updateActionBars();
+ }
+ getCompareConfiguration()
+ .setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTER_ACTIONS,
+ fCompareFilterActions);
+ getCompareConfiguration()
+ .setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTERS_INITIALIZING,
+ null);
+ } else {
+ for (int i = 0; i < fCompareFilterActions.size(); i++) {
+ fCompareFilterActions
+ .get(i).setInput(input, ancestor, left, right);
+ }
+ }
+ }
+ }
+
+ private void disposeCompareFilterActions(boolean updateActionBars) {
+ Iterator<ChangeCompareFilterPropertyAction> compareFilterActionsIterator = fCompareFilterActions
+ .iterator();
+ while (compareFilterActionsIterator.hasNext()) {
+ ChangeCompareFilterPropertyAction compareFilterAction = compareFilterActionsIterator
+ .next();
+ fLeft.removeTextAction(compareFilterAction);
+ fRight.removeTextAction(compareFilterAction);
+ fAncestor.removeTextAction(compareFilterAction);
+ if (updateActionBars
+ && getCompareConfiguration().getContainer().getActionBars() != null) {
+ getCompareConfiguration().getContainer().getActionBars()
+ .getToolBarManager()
+ .remove(compareFilterAction.getId());
+ if (compareFilterAction.getActionDefinitionId() != null)
+ getCompareConfiguration()
+ .getContainer()
+ .getActionBars()
+ .setGlobalActionHandler(
+ compareFilterAction.getActionDefinitionId(),
+ null);
+ }
+ compareFilterAction.dispose();
+ }
+ if (updateActionBars
+ && !fCompareFilterActions.isEmpty()
+ && getCompareConfiguration().getContainer().getActionBars() != null) {
+ getCompareConfiguration().getContainer().getActionBars()
+ .getToolBarManager().markDirty();
+ getCompareConfiguration().getContainer().getActionBars()
+ .getToolBarManager().update(true);
+ }
+ fCompareFilterActions.clear();
+ getCompareConfiguration().setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTERS, null);
+ getCompareConfiguration().setProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTER_ACTIONS, null);
+ }
+
+ @Override
+ protected void handlePropertyChangeEvent(PropertyChangeEvent event) {
+ String key= event.getProperty();
+
+ if (key.equals(CompareConfiguration.IGNORE_WHITESPACE)
+ || key.equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS)
+ || (key.equals(ChangeCompareFilterPropertyAction.COMPARE_FILTERS) && getCompareConfiguration()
+ .getProperty(
+ ChangeCompareFilterPropertyAction.COMPARE_FILTERS_INITIALIZING) == null)) {
+
+ fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);
+
+ update(true);
+ // selectFirstDiff(true);
+ if (fFocusPart != null)
+ handleSelectionChanged(fFocusPart);
+
+// } else if (key.equals(ComparePreferencePage.USE_SPLINES)) {
+// fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
+// invalidateLines();
+
+ } else if (key.equals(ComparePreferencePage.USE_SINGLE_LINE)) {
+ fUseSingleLine= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE);
+// fUseResolveUI= fUseSingleLine;
+ fBasicCenterCurve= null;
+ updateControls();
+ invalidateLines();
+
+ } else if (key.equals(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES)) {
+ fHighlightTokenChanges= fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES);
+ updateControls();
+ updatePresentation();
+
+// } else if (key.equals(ComparePreferencePage.USE_RESOLVE_UI)) {
+// fUseResolveUI= fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI);
+// updateResolveStatus();
+// invalidateLines();
+
+ } else if (key.equals(fSymbolicFontName)) {
+ updateFont();
+ invalidateLines();
+
+ } else if (key.equals(INCOMING_COLOR) || key.equals(OUTGOING_COLOR) || key.equals(CONFLICTING_COLOR) || key.equals(RESOLVED_COLOR)) {
+ updateColors(null);
+ invalidateLines();
+ invalidateTextPresentation();
+
+ } else if (key.equals(ComparePreferencePage.SYNCHRONIZE_SCROLLING)) {
+ boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
+ setSyncScrolling(b);
+
+ } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)) {
+ if (!fIsUsingSystemBackground) {
+ setBackgroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND));
+ }
+
+ } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)) {
+ fIsUsingSystemBackground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT);
+ if (fIsUsingSystemBackground) {
+ setBackgroundColor(null);
+ } else {
+ setBackgroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND));
+ }
+ } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) {
+ if (!fIsUsingSystemForeground) {
+ setForegroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND));
+ }
+
+ } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)) {
+ fIsUsingSystemForeground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT);
+ if (fIsUsingSystemForeground) {
+ setForegroundColor(null);
+ } else {
+ setForegroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND));
+ }
+ } else if (key.equals(ICompareUIConstants.PREF_NAVIGATION_END_ACTION)) {
+ updateControls();
+ } else if (key.equals(CompareContentViewerSwitchingPane.DISABLE_CAPPING_TEMPORARILY)) {
+ if (Boolean.TRUE.equals(event.getNewValue())) {
+ getCompareConfiguration().setProperty(CompareContentViewerSwitchingPane.DISABLE_CAPPING_TEMPORARILY, null);
+ handleCompareInputChange();
+ }
+ } else {
+ super.handlePropertyChangeEvent(event);
+
+ if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) {
+ update(true);
+ selectFirstDiff(true);
+ }
+ }
+ }
+
+ private void selectFirstDiff(boolean first) {
+
+ if (fLeft == null || fRight == null) {
+ return;
+ }
+ if (fLeft.getSourceViewer().getDocument() == null || fRight.getSourceViewer().getDocument() == null) {
+ return;
+ }
+
+ Diff firstDiff= null;
+ if (first)
+ firstDiff= findNext(fRight, -1, -1, false);
+ else
+ firstDiff= findPrev(fRight, 9999999, 9999999, false);
+ setCurrentDiff(firstDiff, true);
+ }
+
+
+
+ private void setSyncScrolling(boolean newMode) {
+ if (fSynchronizedScrolling != newMode) {
+ fSynchronizedScrolling= newMode;
+
+ scrollVertical(0, 0, 0, null);
+
+ // throw away central control (Sash or Canvas)
+ Control center= getCenterControl();
+ if (center != null && !center.isDisposed())
+ center.dispose();
+
+ fLeft.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
+ fRight.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
+
+ fComposite.layout(true);
+ }
+ }
+
+ @Override
+ protected void updateToolItems() {
+ if (fCopyDiffLeftToRightItem != null) {
+ fCopyDiffLeftToRightItem.setVisible(isRightEditable());
+ }
+ if (fCopyDiffRightToLeftItem != null) {
+ fCopyDiffRightToLeftItem.setVisible(isLeftEditable());
+ }
+
+ //only update toolbar items if diffs need to be calculated (which
+ //dictates whether a toolbar gets added at all)
+ if (!isPatchHunk()){
+ if (fIgnoreAncestorItem != null)
+ fIgnoreAncestorItem.setVisible(isThreeWay());
+
+ if (fCopyDiffLeftToRightItem != null) {
+ IAction a= fCopyDiffLeftToRightItem.getAction();
+ if (a != null)
+ a.setEnabled(a.isEnabled() && !fHasErrors);
+ }
+ if (fCopyDiffRightToLeftItem != null) {
+ IAction a= fCopyDiffRightToLeftItem.getAction();
+ if (a != null)
+ a.setEnabled(a.isEnabled() && !fHasErrors);
+ }
+ }
+ super.updateToolItems();
+ }
+
+ //---- painting lines
+
+ private void updateLines(IDocument d) {
+ boolean left= false;
+ boolean right= false;
+
+ // FIXME: this optimization is incorrect because
+ // it doesn't take replace operations into account where
+ // the old and new line count does not differ
+ if (d == fLeft.getSourceViewer().getDocument()) {
+ int l= fLeft.getLineCount();
+ left= fLeftLineCount != l;
+ fLeftLineCount= l;
+ } else if (d == fRight.getSourceViewer().getDocument()) {
+ int l= fRight.getLineCount();
+ right= fRightLineCount != l;
+ fRightLineCount= l;
+ }
+
+ if (left || right) {
+ if (left) {
+ if (fLeftCanvas != null)
+ fLeftCanvas.redraw();
+ } else {
+ if (fRightCanvas != null)
+ fRightCanvas.redraw();
+ }
+ Control center= getCenterControl();
+ if (center != null)
+ center.redraw();
+
+ updateVScrollBar();
+ refreshBirdsEyeView();
+ }
+ }
+
+ private void invalidateLines() {
+ if (isThreeWay() && isAncestorVisible()) {
+ if (Utilities.okToUse(fAncestorCanvas))
+ fAncestorCanvas.redraw();
+ if (fAncestor != null && fAncestor.isControlOkToUse())
+ fAncestor.getSourceViewer().getTextWidget().redraw();
+ }
+
+ if (Utilities.okToUse(fLeftCanvas))
+ fLeftCanvas.redraw();
+
+ if (fLeft != null && fLeft.isControlOkToUse())
+ fLeft.getSourceViewer().getTextWidget().redraw();
+
+ if (Utilities.okToUse(getCenterControl()))
+ getCenterControl().redraw();
+
+ if (fRight != null && fRight.isControlOkToUse())
+ fRight.getSourceViewer().getTextWidget().redraw();
+
+ if (Utilities.okToUse(fRightCanvas))
+ fRightCanvas.redraw();
+ }
+
+ private boolean showResolveUI() {
+ if (!isThreeWay() || isIgnoreAncestor())
+ return false;
+ return isAnySideEditable();
+ }
+
+ private boolean isAnySideEditable() {
+ // we only enable the new resolve UI if exactly one side is editable
+ return isLeftEditable() || isRightEditable();
+ }
+
+ private void paintCenter(Canvas canvas, GC g) {
+
+ Display display= canvas.getDisplay();
+
+ checkForColorUpdate(display);
+
+ if (! fSynchronizedScrolling)
+ return;
+
+ int lineHeightLeft= fLeft.getSourceViewer().getTextWidget().getLineHeight();
+ int lineHeightRight= fRight.getSourceViewer().getTextWidget().getLineHeight();
+ int visibleHeight= fRight.getViewportHeight();
+
+ Point size= canvas.getSize();
+ int x= 0;
+ int w= size.x;
+
+ g.setBackground(canvas.getBackground());
+ g.fillRectangle(x+1, 0, w-2, size.y);
+
+ if (!fIsMotif) {
+ // draw thin line between center ruler and both texts
+ g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
+ g.fillRectangle(0, 0, 1, size.y);
+ g.fillRectangle(w-1, 0, 1, size.y);
+ }
+
+ if (! fHighlightRanges)
+ return;
+
+ if (fMerger.hasChanges()) {
+ int lshift= fLeft.getVerticalScrollOffset();
+ int rshift= fRight.getVerticalScrollOffset();
+
+ Point region= new Point(0, 0);
+
+ for (Iterator<?> iterator = fMerger.changesIterator(); iterator.hasNext();) {
+ Diff diff = (Diff) iterator.next();
+ if (diff.isDeleted())
+ continue;
+
+ if (fShowCurrentOnly2 && !isCurrentDiff(diff))
+ continue;
+
+ fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
+ int ly= (region.x * lineHeightLeft) + lshift;
+ int lh= region.y * lineHeightLeft;
+
+ fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
+ int ry= (region.x * lineHeightRight) + rshift;
+ int rh= region.y * lineHeightRight;
+
+ if (Math.max(ly+lh, ry+rh) < 0)
+ continue;
+ if (Math.min(ly, ry) >= visibleHeight)
+ break;
+
+ fPts[0]= x; fPts[1]= ly; fPts[2]= w; fPts[3]= ry;
+ fPts[6]= x; fPts[7]= ly+lh; fPts[4]= w; fPts[5]= ry+rh;
+
+ Color fillColor= getColor(display, getFillColor(diff));
+ Color strokeColor= getColor(display, getStrokeColor(diff));
+
+ if (fUseSingleLine) {
+ int w2= 3;
+
+ g.setBackground(fillColor);
+ g.fillRectangle(0, ly, w2, lh); // left
+ g.fillRectangle(w-w2, ry, w2, rh); // right
+
+ g.setLineWidth(0 /* LW */);
+ g.setForeground(strokeColor);
+ g.drawRectangle(0-1, ly, w2, lh); // left
+ g.drawRectangle(w-w2, ry, w2, rh); // right
+
+ if (fUseSplines) {
+ int[] points= getCenterCurvePoints(w2, ly+lh/2, w-w2, ry+rh/2);
+ for (int i= 1; i < points.length; i++)
+ g.drawLine(w2+i-1, points[i-1], w2+i, points[i]);
+ } else {
+ g.drawLine(w2, ly+lh/2, w-w2, ry+rh/2);
+ }
+ } else {
+ // two lines
+ if (fUseSplines) {
+ g.setBackground(fillColor);
+
+ g.setLineWidth(0 /* LW */);
+ g.setForeground(strokeColor);
+
+ int[] topPoints= getCenterCurvePoints(fPts[0], fPts[1], fPts[2], fPts[3]);
+ int[] bottomPoints= getCenterCurvePoints(fPts[6], fPts[7], fPts[4], fPts[5]);
+ g.setForeground(fillColor);
+ g.drawLine(0, bottomPoints[0], 0, topPoints[0]);
+ for (int i= 1; i < bottomPoints.length; i++) {
+ g.setForeground(fillColor);
+ g.drawLine(i, bottomPoints[i], i, topPoints[i]);
+ g.setForeground(strokeColor);
+ g.drawLine(i-1, topPoints[i-1], i, topPoints[i]);
+ g.drawLine(i-1, bottomPoints[i-1], i, bottomPoints[i]);
+ }
+ } else {
+ g.setBackground(fillColor);
+ g.fillPolygon(fPts);
+
+ g.setLineWidth(0 /* LW */);
+ g.setForeground(strokeColor);
+ g.drawLine(fPts[0], fPts[1], fPts[2], fPts[3]);
+ g.drawLine(fPts[6], fPts[7], fPts[4], fPts[5]);
+ }
+ }
+
+ if (fUseSingleLine && isAnySideEditable()) {
+ // draw resolve state
+ int cx= (w-RESOLVE_SIZE)/2;
+ int cy= ((ly+lh/2) + (ry+rh/2) - RESOLVE_SIZE)/2;
+
+ g.setBackground(fillColor);
+ g.fillRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE);
+
+ g.setForeground(strokeColor);
+ g.drawRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE);
+ }
+ }
+ }
+ }
+
+ private int[] getCenterCurvePoints(int startx, int starty, int endx, int endy) {
+ if (fBasicCenterCurve == null)
+ buildBaseCenterCurve(endx-startx);
+ double height= endy - starty;
+ height= height/2;
+ int width= endx-startx;
+ int[] points= new int[width];
+ for (int i= 0; i < width; i++) {
+ points[i]= (int) (-height * fBasicCenterCurve[i] + height + starty);
+ }
+ return points;
+ }
+
+ private void buildBaseCenterCurve(int w) {
+ double width= w;
+ fBasicCenterCurve= new double[getCenterWidth()];
+ for (int i= 0; i < getCenterWidth(); i++) {
+ double r= i / width;
+ fBasicCenterCurve[i]= Math.cos(Math.PI * r);
+ }
+ }
+
+ private void paintSides(GC g, MergeSourceViewer tp, Canvas canvas, boolean right) {
+
+ Display display= canvas.getDisplay();
+
+ int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight();
+ int visibleHeight= tp.getViewportHeight();
+
+ Point size= canvas.getSize();
+ int x= 0;
+ int w= fMarginWidth;
+ int w2= w/2;
+
+ g.setBackground(canvas.getBackground());
+ g.fillRectangle(x, 0, w, size.y);
+
+ if (!fIsMotif) {
+ // draw thin line between ruler and text
+ g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
+ if (right)
+ g.fillRectangle(0, 0, 1, size.y);
+ else
+ g.fillRectangle(size.x-1, 0, 1, size.y);
+ }
+
+ if (! fHighlightRanges)
+ return;
+
+ if (fMerger.hasChanges()) {
+ int shift= tp.getVerticalScrollOffset() + (2-LW);
+
+ Point region= new Point(0, 0);
+ 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(leg), region);
+ int y= (region.x * lineHeight) + shift;
+ int h= region.y * lineHeight;
+
+ if (y+h < 0)
+ continue;
+ if (y >= visibleHeight)
+ break;
+
+ g.setBackground(getColor(display, getFillColor(diff)));
+ if (right)
+ g.fillRectangle(x, y, w2, h);
+ else
+ g.fillRectangle(x+w2, y, w2, h);
+
+ g.setLineWidth(0 /* LW */);
+ g.setForeground(getColor(display, getStrokeColor(diff)));
+ if (right)
+ g.drawRectangle(x-1, y-1, w2, h);
+ else
+ g.drawRectangle(x+w2, y-1, w2, h);
+ }
+ }
+ }
+
+ private void paint(PaintEvent event, MergeSourceViewer tp) {
+
+ if (! fHighlightRanges)
+ return;
+ if (!fMerger.hasChanges())
+ return;
+
+ Control canvas= (Control) event.widget;
+ GC g= event.gc;
+
+ Display display= canvas.getDisplay();
+
+ int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight();
+ int w= canvas.getSize().x;
+ int shift= tp.getVerticalScrollOffset() + (2-LW);
+ int maxh= event.y+event.height; // visibleHeight
+
+ //if (fIsMotif)
+ shift+= fTopInset;
+
+ Point range= new Point(0, 0);
+
+ 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(leg), range);
+ int y= (range.x * lineHeight) + shift;
+ int h= range.y * lineHeight;
+
+ if (y+h < event.y)
+ continue;
+ if (y > maxh)
+ break;
+
+ g.setBackground(getColor(display, getStrokeColor(diff)));
+ g.fillRectangle(0, y-1, w, LW);
+ g.fillRectangle(0, y+h-1, w, LW);
+ }
+ }
+
+ private RGB getFillColor(Diff diff) {
+ boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
+ RGB selected_fill= getBackground(null);
+ if (isThreeWay() && !isIgnoreAncestor()) {
+ switch (diff.getKind()) {
+ case RangeDifference.RIGHT:
+ if (!getCompareConfiguration().isMirrored())
+ return selected ? selected_fill : INCOMING_FILL;
+ return selected ? selected_fill : OUTGOING_FILL;
+ case RangeDifference.ANCESTOR:
+ case RangeDifference.CONFLICT:
+ return selected ? selected_fill : CONFLICT_FILL;
+ case RangeDifference.LEFT:
+ if (!getCompareConfiguration().isMirrored())
+ return selected ? selected_fill : OUTGOING_FILL;
+ return selected ? selected_fill : INCOMING_FILL;
+ default:
+ return null;
+ }
+ }
+ return selected ? selected_fill : OUTGOING_FILL;
+ }
+
+ private RGB getStrokeColor(Diff diff) {
+ boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
+
+ if (isThreeWay() && !isIgnoreAncestor()) {
+ switch (diff.getKind()) {
+ case RangeDifference.RIGHT:
+ if (!getCompareConfiguration().isMirrored())
+ return selected ? SELECTED_INCOMING : INCOMING;
+ return selected ? SELECTED_OUTGOING : OUTGOING;
+ case RangeDifference.ANCESTOR:
+ case RangeDifference.CONFLICT:
+ return selected ? SELECTED_CONFLICT : CONFLICT;
+ case RangeDifference.LEFT:
+ if (!getCompareConfiguration().isMirrored())
+ return selected ? SELECTED_OUTGOING : OUTGOING;
+ return selected ? SELECTED_INCOMING : INCOMING;
+ default:
+ return null;
+ }
+ }
+ return selected ? SELECTED_OUTGOING : OUTGOING;
+ }
+
+ private Color getColor(Display display, RGB rgb) {
+ if (rgb == null)
+ return null;
+ if (fColors == null)
+ fColors= new HashMap<RGB, Color>(20);
+ Color c= fColors.get(rgb);
+ if (c == null) {
+ c= new Color(display, rgb);
+ fColors.put(rgb, c);
+ }
+ return c;
+ }
+
+ static RGB interpolate(RGB fg, RGB bg, double scale) {
+ if (fg != null && bg != null)
+ return new RGB(
+ (int)((1.0-scale) * fg.red + scale * bg.red),
+ (int)((1.0-scale) * fg.green + scale * bg.green),
+ (int)((1.0-scale) * fg.blue + scale * bg.blue)
+ );
+ if (fg != null)
+ return fg;
+ if (bg != null)
+ return bg;
+ return new RGB(128, 128, 128); // a gray
+ }
+
+ //---- Navigating and resolving Diffs
+
+ private Diff getNextVisibleDiff(boolean down, boolean deep) {
+ Diff diff= null;
+ MergeSourceViewer part= getNavigationPart();
+ if (part == null)
+ return null;
+ Point s = part.getSourceViewer().getSelectedRange();
+ char leg = getLeg(part);
+ for (;;) {
+ diff = null;
+ diff = internalGetNextDiff(down, deep, part, s);
+ if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
+ && !isAncestorVisible()) {
+ Position position = diff.getPosition(leg);
+ s = new Point(position.getOffset(), position.getLength());
+ diff= null;
+ continue;
+ }
+ break;
+ }
+ return diff;
+ }
+
+ private Diff internalGetNextDiff(boolean down, boolean deep, MergeSourceViewer part, Point s) {
+ if (fMerger.hasChanges()) {
+ if (down)
+ return findNext(part, s.x, s.x+s.y, deep);
+ return findPrev(part, s.x, s.x+s.y, deep);
+ }
+ return null;
+ }
+
+ private MergeSourceViewer getNavigationPart() {
+ MergeSourceViewer part= fFocusPart;
+ if (part == null)
+ part= fRight;
+ return part;
+ }
+
+ private Diff getWrappedDiff(Diff diff, boolean down) {
+ return fMerger.getWrappedDiff(diff, down);
+ }
+
+ /*
+ * Returns true if end (or beginning) of document reached.
+ */
+ private boolean navigate(boolean down, boolean wrap, boolean deep) {
+ Diff diff= null;
+ boolean wrapped = false;
+ for (;;) {
+ diff = getNextVisibleDiff(down, deep);
+ if (diff == null && wrap) {
+ if (wrapped)
+ // We've already wrapped once so break out
+ break;
+ wrapped = true;
+ diff = getWrappedDiff(diff, down);
+ }
+ if (diff != null)
+ setCurrentDiff(diff, true, deep);
+ if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
+ && !isAncestorVisible())
+ continue;
+ break;
+ }
+ return diff == null;
+ }
+
+ private void endOfDocumentReached(boolean down) {
+ Control c= getControl();
+ if (Utilities.okToUse(c)) {
+ handleEndOfDocumentReached(c.getShell(), down);
+ }
+ }
+
+ private void handleEndOfDocumentReached(Shell shell, boolean next) {
+ IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore();
+ String value = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
+ if (!value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) {
+ performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION, next);
+ } else {
+ shell.getDisplay().beep();
+ String loopMessage;
+ String nextMessage;
+ String message;
+ String title;
+ if (next) {
+ title = CompareMessages.TextMergeViewer_0;
+ message = CompareMessages.TextMergeViewer_1;
+ loopMessage = CompareMessages.TextMergeViewer_2;
+ nextMessage = CompareMessages.TextMergeViewer_3;
+ } else {
+ title = CompareMessages.TextMergeViewer_4;
+ message = CompareMessages.TextMergeViewer_5;
+ loopMessage = CompareMessages.TextMergeViewer_6;
+ nextMessage = CompareMessages.TextMergeViewer_7;
+ }
+ String[] localLoopOption = new String[] { loopMessage, ICompareUIConstants.PREF_VALUE_LOOP };
+ String[] nextElementOption = new String[] { nextMessage, ICompareUIConstants.PREF_VALUE_NEXT};
+ String[] doNothingOption = new String[] { CompareMessages.TextMergeViewer_17, ICompareUIConstants.PREF_VALUE_DO_NOTHING};
+ NavigationEndDialog dialog = new NavigationEndDialog(shell,
+ title,
+ null,
+ message,
+ new String[][] {
+ localLoopOption,
+ nextElementOption,
+ doNothingOption
+ });
+ int result = dialog.open();
+ if (result == Window.OK) {
+ performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, next);
+ if (dialog.getToggleState()) {
+ String oldValue = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
+ store.putValue(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL));
+ store.firePropertyChangeEvent(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, oldValue, store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL));
+ }
+ }
+ }
+ }
+
+ private void performEndOfDocumentAction(Shell shell, IPreferenceStore store, String key, boolean next) {
+ String value = store.getString(key);
+ if (value.equals(ICompareUIConstants.PREF_VALUE_DO_NOTHING)) {
+ return;
+ }
+ if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) {
+ ICompareNavigator navigator = getCompareConfiguration()
+ .getContainer().getNavigator();
+ if (hasNextElement(next)) {
+ navigator.selectChange(next);
+ }
+ } else {
+ selectFirstDiff(next);
+ }
+ }
+
+ private boolean hasNextElement(boolean down) {
+ ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
+ if (navigator instanceof CompareNavigator) {
+ CompareNavigator n = (CompareNavigator) navigator;
+ return n.hasChange(down);
+ }
+ return false;
+ }
+
+ /*
+ * Find the Diff that overlaps with the given TextPart's text range.
+ * If the range doesn't overlap with any range <code>null</code>
+ * is returned.
+ */
+ private Diff findDiff(MergeSourceViewer tp, int rangeStart, int rangeEnd) {
+ char contributor = getLeg(tp);
+ return fMerger.findDiff(contributor, rangeStart, rangeEnd);
+ }
+
+ private Diff findNext(MergeSourceViewer tp, int start, int end, boolean deep) {
+ return fMerger.findNext(getLeg(tp), start, end, deep);
+ }
+
+ private Diff findPrev(MergeSourceViewer tp, int start, int end, boolean deep) {
+ return fMerger.findPrev(getLeg(tp), start, end, deep);
+ }
+
+ /*
+ * Set the currently active Diff and update the toolbars controls and lines.
+ * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed and
+ * selected in both TextParts.
+ */
+ private void setCurrentDiff(Diff d, boolean revealAndSelect) {
+ setCurrentDiff(d, revealAndSelect, false);
+ }
+
+ /*
+ * Set the currently active Diff and update the toolbars controls and lines.
+ * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed and
+ * selected in both TextParts.
+ */
+ private void setCurrentDiff(Diff d, boolean revealAndSelect, boolean deep) {
+
+// if (d == fCurrentDiff)
+// return;
+ boolean diffChanged = fCurrentDiff != d;
+
+ if (fLeftToRightButton != null && !fLeftToRightButton.isDisposed())
+ fLeftToRightButton.setVisible(false);
+ if (fRightToLeftButton != null && !fRightToLeftButton.isDisposed())
+ fRightToLeftButton.setVisible(false);
+
+ if (d != null && revealAndSelect) {
+
+ // 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.isToken() || !fHighlightTokenChanges || deep || !d.hasChildren()) {
+ if (isThreeWay() && !isIgnoreAncestor())
+ 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.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
+ saveDiff();
+ fCurrentDiff= d;
+ revealDiff(d, d.isToken());
+ } else {
+ saveDiff();
+ fCurrentDiff= d;
+ }
+
+ updateControls();
+ if (diffChanged)
+ invalidateLines();
+ refreshBirdsEyeView();
+ }
+
+ /*
+ * Smart determines whether
+ */
+ private void revealDiff(Diff d, boolean smart) {
+
+ boolean ancestorIsVisible= false;
+ boolean leftIsVisible= false;
+ boolean rightIsVisible= false;
+
+ if (smart) {
+ Point region= new Point(0, 0);
+ // find the starting line of the diff in all text widgets
+ 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.getPosition(ANCESTOR_CONTRIBUTOR), region).x;
+ if (as >= fAncestor.getSourceViewer().getTopIndex() && as <= fAncestor.getSourceViewer().getBottomIndex())
+ ancestorIsVisible= true;
+ }
+
+ if (ls >= fLeft.getSourceViewer().getTopIndex() && ls <= fLeft.getSourceViewer().getBottomIndex())
+ leftIsVisible= true;
+
+ if (rs >= fRight.getSourceViewer().getTopIndex() && rs <= fRight.getSourceViewer().getBottomIndex())
+ rightIsVisible= true;
+ }
+
+ // vertical scrolling
+ if (!leftIsVisible || !rightIsVisible) {
+ int avpos= 0, lvpos= 0, rvpos= 0;
+
+ MergeSourceViewer allButThis= null;
+ if (leftIsVisible) {
+ avpos= lvpos= rvpos= realToVirtualPosition(LEFT_CONTRIBUTOR, fLeft.getSourceViewer().getTopIndex());
+ allButThis= fLeft;
+ } else if (rightIsVisible) {
+ avpos= lvpos= rvpos= realToVirtualPosition(RIGHT_CONTRIBUTOR, fRight.getSourceViewer().getTopIndex());
+ allButThis= fRight;
+ } else if (ancestorIsVisible) {
+ avpos= lvpos= rvpos= realToVirtualPosition(ANCESTOR_CONTRIBUTOR, fAncestor.getSourceViewer().getTopIndex());
+ allButThis= fAncestor;
+ } else {
+ 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;
+ int delta= fRight.getViewportLines()/4;
+ avpos-= delta;
+ if (avpos < 0)
+ avpos= 0;
+ lvpos-= delta;
+ if (lvpos < 0)
+ lvpos= 0;
+ rvpos-= delta;
+ if (rvpos < 0)
+ rvpos= 0;
+ }
+
+ scrollVertical(avpos, lvpos, rvpos, allButThis);
+
+ if (fVScrollBar != null)
+ fVScrollBar.setSelection(avpos);
+ }
+
+ // horizontal scrolling
+ if (d.isToken()) {
+ // we only scroll horizontally for token diffs
+ 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);
+ hscroll(fLeft);
+ hscroll(fRight);
+ }
+ }
+
+ private static void reveal(MergeSourceViewer v, Position p) {
+ if (v != null && p != null) {
+ StyledText st= v.getSourceViewer().getTextWidget();
+ if (st != null) {
+ Rectangle r= st.getClientArea();
+ if (!r.isEmpty()) // workaround for #7320: Next diff scrolls when going into current diff
+ v.getSourceViewer().revealRange(p.offset, p.length);
+ }
+ }
+ }
+
+ private static void hscroll(MergeSourceViewer v) {
+ if (v != null) {
+ StyledText st= v.getSourceViewer().getTextWidget();
+ if (st != null)
+ st.setHorizontalIndex(0);
+ }
+ }
+
+ //--------------------------------------------------------------------------------
+
+ void copyAllUnresolved(boolean leftToRight) {
+ if (fMerger.hasChanges() && isThreeWay() && !isIgnoreAncestor()) {
+ IRewriteTarget target= leftToRight ? fRight.getSourceViewer().getRewriteTarget() : fLeft.getSourceViewer().getRewriteTarget();
+ boolean compoundChangeStarted= false;
+ try {
+ for (Iterator<?> iterator = fMerger.changesIterator(); iterator.hasNext();) {
+ Diff diff = (Diff) iterator.next();
+ switch (diff.getKind()) {
+ case RangeDifference.LEFT:
+ if (leftToRight) {
+ if (!compoundChangeStarted) {
+ target.beginCompoundChange();
+ compoundChangeStarted= true;
+ }
+ copy(diff, leftToRight);
+ }
+ break;
+ case RangeDifference.RIGHT:
+ if (!leftToRight) {
+ if (!compoundChangeStarted) {
+ target.beginCompoundChange();
+ compoundChangeStarted= true;
+ }
+ copy(diff, leftToRight);
+ }
+ break;
+ default:
+ continue;
+ }
+ }
+ } finally {
+ if (compoundChangeStarted) {
+ target.endCompoundChange();
+ }
+ }
+ }
+ }
+
+ /*
+ * Copy whole document from one side to the other.
+ */
+ @Override
+ protected void copy(boolean leftToRight) {
+ if (!validateChange(!leftToRight))
+ return;
+ if (showResolveUI()) {
+ copyAllUnresolved(leftToRight);
+ invalidateLines();
+ return;
+ }
+ copyOperationInProgress = true;
+ if (leftToRight) {
+ if (fLeft.getEnabled()) {
+ // copy text
+ String text= fLeft.getSourceViewer().getTextWidget().getText();
+ fRight.getSourceViewer().getTextWidget().setText(text);
+ fRight.setEnabled(true);
+ } else {
+ // delete
+ fRight.getSourceViewer().getTextWidget().setText(""); //$NON-NLS-1$
+ fRight.setEnabled(false);
+ }
+ fRightLineCount= fRight.getLineCount();
+ setRightDirty(true);
+ } else {
+ if (fRight.getEnabled()) {
+ // copy text
+ String text= fRight.getSourceViewer().getTextWidget().getText();
+ fLeft.getSourceViewer().getTextWidget().setText(text);
+ fLeft.setEnabled(true);
+ } else {
+ // delete
+ fLeft.getSourceViewer().getTextWidget().setText(""); //$NON-NLS-1$
+ fLeft.setEnabled(false);
+ }
+ fLeftLineCount= fLeft.getLineCount();
+ setLeftDirty(true);
+ }
+ copyOperationInProgress = false;
+ update(false);
+ selectFirstDiff(true);
+ }
+
+ private void historyNotification(OperationHistoryEvent event) {
+ switch (event.getEventType()) {
+ case OperationHistoryEvent.OPERATION_ADDED:
+ if (copyOperationInProgress) {
+ copyUndoable = event.getOperation();
+ }
+ break;
+ case OperationHistoryEvent.UNDONE:
+ if (copyUndoable == event.getOperation()) {
+ update(false);
+ }
+ break;
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+
+ private void copyDiffLeftToRight() {
+ copy(fCurrentDiff, true, false);
+ }
+
+ private void copyDiffRightToLeft() {
+ copy(fCurrentDiff, false, false);
+ }
+
+ /*
+ * Copy the contents of the given diff from one side to the other.
+ */
+ private void copy(Diff diff, boolean leftToRight, boolean gotoNext) {
+ if (copy(diff, leftToRight)) {
+ if (gotoNext) {
+ navigate(true, true, false /* don't step in */);
+ } else {
+ revealDiff(diff, true);
+ updateControls();
+ }
+ }
+ }
+
+ /*
+ * Copy the contents of the given diff from one side to the other but
+ * doesn't reveal anything.
+ * Returns true if copy was successful.
+ */
+ private boolean copy(Diff diff, boolean leftToRight) {
+
+ if (diff != null) {
+ if (!validateChange(!leftToRight))
+ return false;
+ if (leftToRight) {
+ fRight.setEnabled(true);
+ } else {
+ fLeft.setEnabled(true);
+ }
+ boolean result = fMerger.copy(diff, leftToRight);
+ if (result)
+ updateResolveStatus();
+ return result;
+ }
+ return false;
+ }
+
+ private boolean validateChange(boolean left) {
+ ContributorInfo info;
+ if (left)
+ info = fLeftContributor;
+ else
+ info = fRightContributor;
+
+ return info.validateChange();
+ }
+
+ //---- scrolling
+
+ /*
+ * The height of the TextEditors in lines.
+ */
+ private int getViewportHeight() {
+ StyledText te= fLeft.getSourceViewer().getTextWidget();
+
+ int vh= te.getClientArea().height;
+ if (vh == 0) {
+ Rectangle trim= te.computeTrim(0, 0, 0, 0);
+ int scrollbarHeight= trim.height;
+
+ int headerHeight= getHeaderHeight();
+
+ Composite composite= (Composite) getControl();
+ Rectangle r= composite.getClientArea();
+
+ vh= r.height-headerHeight-scrollbarHeight;
+ }
+
+ return vh / te.getLineHeight();
+ }
+
+ /*
+ * Returns the virtual position for the given view position.
+ */
+ private int realToVirtualPosition(char contributor, int vpos) {
+ if (! fSynchronizedScrolling)
+ return vpos;
+ return fMerger.realToVirtualPosition(contributor, vpos);
+ }
+
+ private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) {
+
+ int s= 0;
+
+ if (fSynchronizedScrolling) {
+ s= fMerger.getVirtualHeight() - rvpos;
+ int height= fRight.getViewportLines()/4;
+ if (s < 0)
+ s= 0;
+ if (s > height)
+ s= height;
+ }
+
+ fInScrolling= true;
+
+ if (isThreeWay() && allBut != fAncestor) {
+ if (fSynchronizedScrolling || allBut == null) {
+ int y= virtualToRealPosition(ANCESTOR_CONTRIBUTOR, avpos+s)-s;
+ fAncestor.vscroll(y);
+ }
+ }
+
+ if (allBut != fLeft) {
+ if (fSynchronizedScrolling || allBut == null) {
+ int y= virtualToRealPosition(LEFT_CONTRIBUTOR, lvpos+s)-s;
+ fLeft.vscroll(y);
+ }
+ }
+
+ if (allBut != fRight) {
+ if (fSynchronizedScrolling || allBut == null) {
+ int y= virtualToRealPosition(RIGHT_CONTRIBUTOR, rvpos+s)-s;
+ fRight.vscroll(y);
+ }
+ }
+
+ fInScrolling= false;
+
+ if (isThreeWay() && fAncestorCanvas != null)
+ fAncestorCanvas.repaint();
+
+ if (fLeftCanvas != null)
+ fLeftCanvas.repaint();
+
+ Control center= getCenterControl();
+ if (center instanceof BufferedCanvas)
+ ((BufferedCanvas) center).repaint();
+
+ if (fRightCanvas != null)
+ fRightCanvas.repaint();
+ }
+
+ /*
+ * Updates Scrollbars with viewports.
+ */
+ private void syncViewport(MergeSourceViewer w) {
+
+ if (fInScrolling)
+ return;
+
+ int ix= w.getSourceViewer().getTopIndex();
+ int ix2= w.getDocumentRegionOffset();
+
+ 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, fMerger.getVirtualHeight() - getViewportHeight()));
+ fVScrollBar.setSelection(value);
+ //refreshBirdEyeView();
+ }
+ }
+
+ /**
+ */
+ private void updateVScrollBar() {
+
+ if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) {
+ int virtualHeight= fMerger.getVirtualHeight();
+ int viewPortHeight= getViewportHeight();
+ int pageIncrement= viewPortHeight-1;
+ int thumb= (viewPortHeight > virtualHeight) ? virtualHeight : viewPortHeight;
+
+ fVScrollBar.setPageIncrement(pageIncrement);
+ fVScrollBar.setMaximum(virtualHeight);
+ fVScrollBar.setThumb(thumb);
+ }
+ }
+
+ /*
+ * maps given virtual position into a real view position of this view.
+ */
+ private int virtualToRealPosition(char contributor, int v) {
+ if (! fSynchronizedScrolling)
+ return v;
+ return fMerger.virtualToRealPosition(contributor, v);
+ }
+
+ @Override
+ void flushLeftSide(Object oldInput, IProgressMonitor monitor){
+ IMergeViewerContentProvider content= getMergeContentProvider();
+ Object leftContent = content.getLeftContent(oldInput);
+
+ if (leftContent != null && isLeftEditable() && isLeftDirty()) {
+ if (fLeftContributor.hasSharedDocument(leftContent)) {
+ if (flush(fLeftContributor))
+ setLeftDirty(false);
+ }
+ }
+
+ if (!(content instanceof MergeViewerContentProvider) || isLeftDirty()) {
+ super.flushLeftSide(oldInput, monitor);
+ }
+ }
+
+ @Override
+ void flushRightSide(Object oldInput, IProgressMonitor monitor){
+ IMergeViewerContentProvider content= getMergeContentProvider();
+ Object rightContent = content.getRightContent(oldInput);
+
+ if (rightContent != null && isRightEditable() && isRightDirty()) {
+ if (fRightContributor.hasSharedDocument(rightContent)) {
+ if (flush(fRightContributor))
+ setRightDirty(false);
+ }
+ }
+
+ if (!(content instanceof MergeViewerContentProvider) || isRightDirty()) {
+ super.flushRightSide(oldInput, monitor);
+ }
+ }
+
+ @Override
+ protected void flushContent(Object oldInput, IProgressMonitor monitor) {
+ flushLeftSide(oldInput, monitor);
+ flushRightSide(oldInput, monitor);
+
+ IMergeViewerContentProvider content = getMergeContentProvider();
+
+ if (!(content instanceof MergeViewerContentProvider) || isLeftDirty() || isRightDirty()) {
+ super.flushContent(oldInput, monitor);
+ }
+ }
+
+ private boolean flush(final ContributorInfo info) {
+ try {
+ return info.flush();
+ } catch (CoreException e) {
+ handleException(e);
+ }
+ return false;
+ }
+
+ private void handleException(Throwable throwable) {
+ // TODO: Should show error to the user
+ if (throwable instanceof InvocationTargetException) {
+ InvocationTargetException ite = (InvocationTargetException) throwable;
+ handleException(ite.getTargetException());
+ return;
+ }
+ CompareUIPlugin.log(throwable);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getAdapter(Class<T> adapter) {
+ if (adapter == IMergeViewerTestAdapter.class) {
+ return (T) new IMergeViewerTestAdapter() {
+ @Override
+ public IDocument getDocument(char leg) {
+ switch (leg) {
+ case LEFT_CONTRIBUTOR:
+ return fLeft.getSourceViewer().getDocument();
+ case RIGHT_CONTRIBUTOR:
+ return fRight.getSourceViewer().getDocument();
+ case ANCESTOR_CONTRIBUTOR:
+ return fAncestor.getSourceViewer().getDocument();
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public int getChangesCount() {
+ return fMerger.changesCount();
+ }
+ };
+ }
+ if (adapter == OutlineViewerCreator.class) {
+ if (fOutlineViewerCreator == null)
+ fOutlineViewerCreator = new InternalOutlineViewerCreator();
+ return (T) fOutlineViewerCreator;
+
+ }
+ if (adapter == IFindReplaceTarget.class)
+ return (T) getFindReplaceTarget();
+ if (adapter == CompareHandlerService.class)
+ return (T) fHandlerService;
+ if (adapter == CompareHandlerService[].class) {
+ return (T) new CompareHandlerService[] { fHandlerService,
+ super.getCompareHandlerService() };
+ }
+ if (adapter == IEditorInput.class) {
+ // return active editor input
+ if (fLeft != null && fLeft == fFocusPart)
+ if (fLeftContributor != null)
+ return (T) fLeftContributor.getDocumentKey();
+ if (fRight != null && fRight == fFocusPart)
+ if (fRightContributor != null)
+ return (T) fRightContributor.getDocumentKey();
+ if (fAncestor != null && fAncestor == fFocusPart)
+ if (fAncestorContributor != null)
+ return (T) fAncestorContributor.getDocumentKey();
+ }
+ return null;
+ }
+
+ @Override
+ protected void handleCompareInputChange() {
+ try {
+ beginRefresh();
+ super.handleCompareInputChange();
+ } finally {
+ endRefresh();
+ }
+ }
+
+ private void beginRefresh() {
+ isRefreshing++;
+ fLeftContributor.cacheSelection(fLeft);
+ fRightContributor.cacheSelection(fRight);
+ fAncestorContributor.cacheSelection(fAncestor);
+ if (fSynchronizedScrolling) {
+ fSynchronziedScrollPosition = fVScrollBar.getSelection();
+ }
+
+ }
+
+ private void endRefresh() {
+ isRefreshing--;
+ fLeftContributor.cacheSelection(null);
+ fRightContributor.cacheSelection(null);
+ fAncestorContributor.cacheSelection(null);
+ fSynchronziedScrollPosition = -1;
+ }
+
+ private void synchronizedScrollVertical(int vpos) {
+ scrollVertical(vpos, vpos, vpos, null);
+ }
+
+ private boolean isIgnoreAncestor() {
+ return Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false);
+ }
+
+ /* package */ void update(boolean includeControls) {
+ if (getControl().isDisposed())
+ return;
+ if (fHasErrors) {
+ resetDiffs();
+ } else {
+ doDiff();
+ }
+
+ if (includeControls)
+ updateControls();
+
+ updateVScrollBar();
+ updatePresentation();
+ }
+
+ private void resetDiffs() {
+ // clear stuff
+ saveDiff();
+ fCurrentDiff= null;
+ fMerger.reset();
+ resetPositions(fLeft.getSourceViewer().getDocument());
+ resetPositions(fRight.getSourceViewer().getDocument());
+ resetPositions(fAncestor.getSourceViewer().getDocument());
+ }
+
+ private boolean isPatchHunk() {
+ return Utilities.isHunk(getInput());
+ }
+
+ private boolean isPatchHunkOk() {
+ if (isPatchHunk())
+ return Utilities.isHunkOk(getInput());
+ return false;
+ }
+
+ /**
+ * Return the provided start position of the hunk in the target file.
+ * @return the provided start position of the hunk in the target file
+ */
+ private int getHunkStart() {
+ Object input = getInput();
+ if (input != null && input instanceof DiffNode){
+ ITypedElement right = ((DiffNode) input).getRight();
+ if (right != null) {
+ Object element = Adapters.adapt(right, IHunk.class);
+ if (element instanceof IHunk)
+ return ((IHunk) element).getStartPosition();
+ }
+ ITypedElement left = ((DiffNode) input).getLeft();
+ if (left != null) {
+ Object element = Adapters.adapt(left, IHunk.class);
+ if (element instanceof IHunk)
+ return ((IHunk) element).getStartPosition();
+ }
+ }
+ return 0;
+ }
+
+ private IFindReplaceTarget getFindReplaceTarget() {
+ if (fFindReplaceTarget == null)
+ 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;
+ }
+
+ private boolean isCurrentDiff(Diff diff) {
+ if (diff == null)
+ return false;
+ if (diff == fCurrentDiff)
+ return true;
+ if (fCurrentDiff != null && fCurrentDiff.getParent() == diff)
+ return true;
+ return false;
+ }
+
+ private 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;
+ }
+
+ /**
+ * This method returns {@link ITextEditor} used in the
+ * {@link ChangeEncodingAction}. It provides implementation of methods that
+ * are used by the action by delegating them to {@link ContributorInfo} that
+ * corresponds to the side that has focus.
+ *
+ * @return the text editor adapter
+ */
+ private ITextEditor getTextEditorAdapter() {
+ return new ITextEditor() {
+ @Override
+ public void close(boolean save) {
+ }
+ @Override
+ public void doRevertToSaved() {
+ }
+ @Override
+ public IAction getAction(String actionId) {
+ return null;
+ }
+ @Override
+ public IDocumentProvider getDocumentProvider() {
+ return null;
+ }
+ @Override
+ public IRegion getHighlightRange() {
+ return null;
+ }
+ @Override
+ public ISelectionProvider getSelectionProvider() {
+ return null;
+ }
+
+ @Override
+ public boolean isEditable() {
+ return false;
+ }
+
+ @Override
+ public void removeActionActivationCode(String actionId) {
+ }
+
+ @Override
+ public void resetHighlightRange() {
+ }
+
+ @Override
+ public void selectAndReveal(int offset, int length) {
+ }
+
+ @Override
+ public void setAction(String actionId, IAction action) {
+ }
+
+ @Override
+ public void setActionActivationCode(String actionId,
+ char activationCharacter, int activationKeyCode,
+ int activationStateMask) {
+ }
+
+ @Override
+ public void setHighlightRange(int offset, int length, boolean moveCursor) {
+ }
+
+ @Override
+ public void showHighlightRangeOnly(boolean showHighlightRangeOnly) {
+ }
+
+ @Override
+ public boolean showsHighlightRangeOnly() {
+ return false;
+ }
+
+ @Override
+ public IEditorInput getEditorInput() {
+ if (fFocusPart == fAncestor && fAncestorContributor != null) {
+ return fAncestorContributor.getDocumentKey();
+ } else if (fFocusPart == fLeft && fLeftContributor != null) {
+ return fLeftContributor.getDocumentKey();
+ } else if (fFocusPart == fRight && fRightContributor != null) {
+ return fRightContributor.getDocumentKey();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public IEditorSite getEditorSite() {
+ return null;
+ }
+
+ @Override
+ public void init(IEditorSite site, IEditorInput input)
+ throws PartInitException {
+ }
+
+ @Override
+ public void addPropertyListener(IPropertyListener listener) {
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public IWorkbenchPartSite getSite() {
+ return new IWorkbenchPartSite() {
+ @Override
+ public String getId() {
+ return null;
+ }
+ @Override
+ @Deprecated
+ public IKeyBindingService getKeyBindingService() {
+ return null;
+ }
+ @Override
+ public IWorkbenchPart getPart() {
+ return null;
+ }
+ @Override
+ public String getPluginId() {
+ return null;
+ }
+ @Override
+ public String getRegisteredName() {
+ return null;
+ }
+ @Override
+ public void registerContextMenu(MenuManager menuManager,
+ ISelectionProvider selectionProvider) {
+ }
+ @Override
+ public void registerContextMenu(String menuId,
+ MenuManager menuManager,
+ ISelectionProvider selectionProvider) {
+ }
+ @Override
+ public IWorkbenchPage getPage() {
+ return null;
+ }
+ @Override
+ public ISelectionProvider getSelectionProvider() {
+ return null;
+ }
+ @Override
+ public Shell getShell() {
+ return fComposite.getShell();
+ }
+ @Override
+ public IWorkbenchWindow getWorkbenchWindow() {
+ return null;
+ }
+ @Override
+ public void setSelectionProvider(ISelectionProvider provider) {
+ }
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ return null;
+ }
+ @Override
+ public <T> T getService(Class<T> api) {
+ return null;
+ }
+ @Override
+ public boolean hasService(Class<?> api) {
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public String getTitle() {
+ return null;
+ }
+
+ @Override
+ public Image getTitleImage() {
+ return null;
+ }
+
+ @Override
+ public String getTitleToolTip() {
+ return null;
+ }
+
+ @Override
+ public void removePropertyListener(IPropertyListener listener) {
+ }
+
+ @Override
+ public void setFocus() {
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getAdapter(Class<T> adapter) {
+ if (adapter == IEncodingSupport.class) {
+ if (fFocusPart == fAncestor) {
+ return (T) getEncodingSupport(fAncestorContributor);
+ } else if (fFocusPart == fLeft) {
+ return (T) getEncodingSupport(fLeftContributor);
+ } else if (fFocusPart == fRight) {
+ return (T) getEncodingSupport(fRightContributor);
+ }
+ }
+ return null;
+ }
+
+ private IEncodingSupport getEncodingSupport(ContributorInfo contributor) {
+ if (contributor != null && contributor.getDefaultEncoding() != null) {
+ return contributor;
+ }
+ return null;
+ }
+
+ @Override
+ public void doSave(IProgressMonitor monitor) {
+ }
+
+ @Override
+ public void doSaveAs() {
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (fFocusPart == fLeft) {
+ return isLeftDirty();
+ } else if (fFocusPart == fRight) {
+ return isRightDirty();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSaveAsAllowed() {
+ // Implementing interface method
+ return false;
+ }
+
+ @Override
+ public boolean isSaveOnCloseNeeded() {
+ // Implementing interface method
+ return false;
+ }
+ };
+ }
+
+ private void updateStructure() {
+ getCompareConfiguration().setProperty("ALL_STRUCTURE_REFRESH", null); //$NON-NLS-1$
+ }
+
+ private void updateStructure(char leg) {
+ String key = null;
+ switch (leg) {
+ case ANCESTOR_CONTRIBUTOR:
+ key = "ANCESTOR_STRUCTURE_REFRESH"; //$NON-NLS-1$
+ break;
+ case LEFT_CONTRIBUTOR:
+ key = "LEFT_STRUCTURE_REFRESH"; //$NON-NLS-1$
+ break;
+ case RIGHT_CONTRIBUTOR:
+ key = "RIGHT_STRUCTURE_REFRESH"; //$NON-NLS-1$
+ break;
+ default:
+ break;
+ }
+ Assert.isNotNull(key);
+ getCompareConfiguration().setProperty(key, null);
+ }
+}

Back to the top