diff options
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.java | 5680 |
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); + } +} |