blob: 7c6f260f853570ba709ea849049bb8a8a5c2db6f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* 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.Optional;
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.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.CursorLinePainter;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IFindReplaceTargetExtension;
import org.eclipse.jface.text.IFindReplaceTargetExtension3;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.TextViewer;
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.DisposeEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
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.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TypedListener;
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>getXContent</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 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;
// 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) {
textWidget.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 Optional.ofNullable(getTarget()).map(
target -> target.findAndSelect(widgetOffset, findString, searchForward, caseSensitive, wholeWord))
.orElse(-1);
}
private IFindReplaceTarget getTarget() {
return Optional.ofNullable(fFocusPart).map(MergeSourceViewer::getSourceViewer)
.map(SourceViewer::getFindReplaceTarget)
.orElse(null);
}
@Override
public Point getSelection() {
return Optional.ofNullable(getTarget()).map(target -> target.getSelection())
.orElse(new Point(-1, -1));
}
@Override
public String getSelectionText() {
return Optional.ofNullable(getTarget()).map(IFindReplaceTarget::getSelectionText).orElse(""); //$NON-NLS-1$
}
@Override
public boolean isEditable() {
return Optional.ofNullable(getTarget()).map(IFindReplaceTarget::isEditable).orElse(false);
}
@Override
public void replaceSelection(String text) {
Optional.ofNullable(getTarget()).ifPresent(target -> target.replaceSelection(text));
}
@Override
public int findAndSelect(int offset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {
IFindReplaceTarget findReplaceTarget = getTarget();
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 = getTarget();
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 = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension2) {
return ((IFindReplaceTargetExtension2) findReplaceTarget).validateTargetState();
}
// TODO not sure if true when findReplaceTarget is null
return true;
}
@Override
public void beginSession() {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) findReplaceTarget).beginSession();
}
}
@Override
public void endSession() {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) findReplaceTarget).endSession();
}
}
@Override
public IRegion getScope() {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
return ((IFindReplaceTargetExtension) findReplaceTarget).getScope();
}
return null;
}
@Override
public void setScope(IRegion scope) {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) findReplaceTarget).setScope(scope);
}
}
@Override
public Point getLineSelection() {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
return ((IFindReplaceTargetExtension) findReplaceTarget).getLineSelection();
}
return null;
}
@Override
public void setSelection(int offset, int length) {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) findReplaceTarget).setSelection(offset, length);
}
}
@Override
public void setScopeHighlightColor(Color color) {
IFindReplaceTarget findReplaceTarget = getTarget();
if (findReplaceTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) findReplaceTarget).setScopeHighlightColor(color);
}
}
@Override
public void setReplaceAllMode(boolean replaceAll) {
IFindReplaceTarget findReplaceTarget = getTarget();
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();
fIsMac= Util.isMac();
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);
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.
*/
@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 (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= e.widget.getDisplay().getSystemCursor(SWT.CURSOR_HAND);
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 = 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);
Cursor normalCursor= canvas.getDisplay().getSystemCursor(SWT.CURSOR_ARROW);
int style= fIsMac ? SWT.FLAT : SWT.PUSH;
fLeftToRightButton= new Button(canvas, style);
fLeftToRightButton.setCursor(normalCursor);
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(normalCursor);
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(fRight.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) {
return;
}
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);
// 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);
// 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
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);
}
}