diff options
author | Jeremy Flicker | 2012-11-28 11:03:52 +0000 |
---|---|---|
committer | Malgorzata Janczarska | 2013-04-03 08:20:50 +0000 |
commit | bf225ea8f252cddb2686012f4c44cc8040d627d2 (patch) | |
tree | 8e9697e52038851be88397e355ac07a109fa10ed | |
parent | 41435564a99c604566f10591e675ce10072b8133 (diff) | |
download | eclipse.platform.team-change/9004/1.tar.gz eclipse.platform.team-change/9004/1.tar.xz eclipse.platform.team-change/9004/1.zip |
Bug 382427 - Mechanism to inject line compare strategies into merge andchange/9004/1
structure viewers
Change-Id: Iec597bd9582933b90f67de94547b4ccdaef9a319
19 files changed, 1156 insertions, 57 deletions
diff --git a/bundles/org.eclipse.compare/META-INF/MANIFEST.MF b/bundles/org.eclipse.compare/META-INF/MANIFEST.MF index 016995ab0..de3405265 100644 --- a/bundles/org.eclipse.compare/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.compare/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.compare; singleton:=true -Bundle-Version: 3.5.400.qualifier +Bundle-Version: 3.6.0.qualifier Bundle-Activator: org.eclipse.compare.internal.CompareUIPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareStrategy.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareStrategy.java new file mode 100644 index 000000000..82ea5fad4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareStrategy.java @@ -0,0 +1,85 @@ +package org.eclipse.compare;
+
+import java.util.HashMap;
+
+import org.eclipse.jface.text.IRegion;
+
+/*******************************************************************************
+ * Copyright (c) 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+/**
+ * A strategy that can be used to customize the detection of text differences
+ * via the compareStrategies extension point Strategies are exposed as toggle
+ * actions in the compare viewer.
+ *
+ * @since 3.6
+ */
+public interface ICompareStrategy {
+
+ /**
+ * Key for the <code>String</code> of the line of text being compared.
+ */
+ public static final String THIS_LINE= "THIS_LINE"; //$NON-NLS-1$
+
+ /**
+ * Key for the <code>Character</code> representing contributor of this line.
+ * Value is either 'A' for ancestor, 'L' for left, or 'R' for right.
+ */
+ public static final String THIS_CONTRIBUTOR= "THIS_CONTRIBUTOR"; //$NON-NLS-1$
+
+ /**
+ * Key for the <code>String</code> of the line of text this line is being compared to.
+ */
+ public static final String OTHER_LINE= "OTHER_LINE"; //$NON-NLS-1$
+
+ /**
+ * Key for the <code>Character</code> representing contributor of the other line.
+ * Value is either 'A' for ancestor, 'L' for left, or 'R' for right.
+ */
+ public static final String OTHER_CONTRIBUTOR= "OTHER_CONTRIBUTOR"; //$NON-NLS-1$
+
+ /**
+ * Forwards the current input objects of the compare
+ * @param input the merge viewer input
+ * @param ancestor input into ancestor viewer
+ * @param left input into left viewer
+ * @param right input into right viewer
+ */
+ public void setInput(Object input, Object ancestor, Object left, Object right);
+
+ /**
+ * Identifies the regions of a line of text in a comparison that should
+ * be ignored for comparison purposes.
+ *
+ * @param lineComparison contains values for the keys <CODE>THIS_LINE</CODE>,
+ * <CODE>THIS_CONTRIBUTOR</CODE>, <CODE>OTHER_LINE</CODE>
+ * and <CODE>OTHER_CONTRIBUTOR</CODE>
+ * @return Regions of <code>THIS_LINE</code> to be ignored for comparison purposes.
+ */
+ public IRegion[] ignoreRegions(HashMap lineComparison);
+
+ /**
+ * Returns whether the strategy should be enabled when first initialized
+ *
+ * @return default enablement
+ */
+ public boolean isEnabledInitially();
+
+ /**
+ * Because the comparison routine may compare each line multiple times to other lines,
+ * the ignored regions may need to be calculated multiple times for the same line during
+ * a comparison. If the ignored regions for each line will be the same regardless of what
+ * line it is being compared to, returning <code>true</code> to this method will cause the
+ * ignored region calculations to be re-used and improve the performance of the comparison.
+ * @return ignored region results can be cached
+ */
+ public boolean areIgnoredRegionsReusable();
+}
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java index 28ee9c1b9..02395c5ea 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java @@ -39,12 +39,15 @@ 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.ChangeCompareStrategyPropertyAction; import org.eclipse.compare.internal.ChangePropertyAction; import org.eclipse.compare.internal.CompareEditor; +import org.eclipse.compare.internal.CompareEditorContributor; import org.eclipse.compare.internal.CompareEditorSelectionProvider; import org.eclipse.compare.internal.CompareHandlerService; import org.eclipse.compare.internal.CompareMessages; import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareStrategyDescriptor; import org.eclipse.compare.internal.CompareUIPlugin; import org.eclipse.compare.internal.DocumentManager; import org.eclipse.compare.internal.ICompareContextIds; @@ -426,6 +429,7 @@ public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { private TextEditorPropertyAction toggleLineNumbersAction; private IFindReplaceTarget fFindReplaceTarget; private ChangePropertyAction fIgnoreWhitespace; + private List fCompareStrategyActions = new ArrayList(); private DocumentMerger fMerger; /** The current diff */ private Diff fCurrentDiff; @@ -1852,7 +1856,10 @@ public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { if (fIgnoreWhitespace != null) fIgnoreWhitespace.dispose(); - + + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES_INITIALIZING, Boolean.TRUE); + disposeCompareStrategiesActions(); + if (fSourceViewerDecorationSupport != null) { for (Iterator iterator = fSourceViewerDecorationSupport.iterator(); iterator.hasNext();) { ((SourceViewerDecorationSupport) iterator.next()).dispose(); @@ -2790,6 +2797,8 @@ public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { Object input= getInput(); + configureCompareStrategiesActions(input, ancestor, left, right); + Position leftRange= null; Position rightRange= null; @@ -3720,6 +3729,85 @@ public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { fHandlerService.registerAction(toggleLineNumbersAction, ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE); } + private void configureCompareStrategiesActions(Object input, Object ancestor, Object left, Object right) { + CompareStrategyDescriptor[] compareStrategyDescriptors = CompareUIPlugin + .getDefault().findCompareStrategies(input); + + Object current = getCompareConfiguration().getProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGY_ACTIONS); + boolean currentStrategiesMatch = false; + if (current!=null && current instanceof List && ((List)current).size()==compareStrategyDescriptors.length) { + currentStrategiesMatch = true; + List currentStrategyActions = (List)current; + for (int i = 0; i<compareStrategyDescriptors.length; i++) { + boolean match = false; + for (int j=0;j<currentStrategyActions.size();j++) { + if (compareStrategyDescriptors[i].getStrategyId().equals( + ((ChangeCompareStrategyPropertyAction)currentStrategyActions.get(j)).getStrategyId())) { + match = true; + break; + } + } + if (!match) { + currentStrategiesMatch = false; + break; + } + } + } + + if (!currentStrategiesMatch) { + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES_INITIALIZING, Boolean.TRUE); + disposeCompareStrategiesActions(); + fCompareStrategyActions.clear(); + for (int i = 0; i<compareStrategyDescriptors.length; i++) { + ChangeCompareStrategyPropertyAction compareStrategyAction = new ChangeCompareStrategyPropertyAction( + compareStrategyDescriptors[i], getCompareConfiguration()); + compareStrategyAction.setInput(input, ancestor, left, right); + fCompareStrategyActions.add(compareStrategyAction); + fLeft.addTextAction(compareStrategyAction); + fRight.addTextAction(compareStrategyAction); + fAncestor.addTextAction(compareStrategyAction); + + if (getCompareConfiguration().getContainer().getActionBars()!=null) { + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().appendToGroup(CompareEditorContributor.STRATEGY_SEPARATOR, compareStrategyAction); + } + } + if (!fCompareStrategyActions.isEmpty() && getCompareConfiguration().getContainer().getActionBars()!=null) { + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().markDirty(); + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().update(true); + getCompareConfiguration().getContainer().getActionBars().updateActionBars(); + } + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGY_ACTIONS, fCompareStrategyActions); + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES_INITIALIZING, null); + } else { + for (int i = 0; i<fCompareStrategyActions.size(); i++) { + ((ChangeCompareStrategyPropertyAction)fCompareStrategyActions.get(i)).setInput(input, ancestor, left, right); + } + } + } + + private void disposeCompareStrategiesActions() { + Iterator compareStrategyActionsIterator = fCompareStrategyActions + .iterator(); + while (compareStrategyActionsIterator.hasNext()) { + ChangeCompareStrategyPropertyAction compareStrategyAction = (ChangeCompareStrategyPropertyAction) compareStrategyActionsIterator + .next(); + fLeft.removeTextAction(compareStrategyAction); + fRight.removeTextAction(compareStrategyAction); + fAncestor.removeTextAction(compareStrategyAction); + if (getCompareConfiguration().getContainer().getActionBars()!=null) { + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().remove(compareStrategyAction.getId()); + } + compareStrategyAction.dispose(); + } + if (!fCompareStrategyActions.isEmpty() && getCompareConfiguration().getContainer().getActionBars()!=null) { + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().markDirty(); + getCompareConfiguration().getContainer().getActionBars().getToolBarManager().update(true); + } + fCompareStrategyActions.clear(); + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES, null); + getCompareConfiguration().setProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGY_ACTIONS, null); + } + /* (non-Javadoc) * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handlePropertyChangeEvent(org.eclipse.jface.util.PropertyChangeEvent) */ @@ -3727,7 +3815,9 @@ public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { String key= event.getProperty(); if (key.equals(CompareConfiguration.IGNORE_WHITESPACE) - || key.equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS)) { + || key.equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS) + || (key.equals(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES) && + getCompareConfiguration().getProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES_INITIALIZING)==null)) { fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS); diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangeCompareStrategyPropertyAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangeCompareStrategyPropertyAction.java new file mode 100644 index 000000000..fab26602e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangeCompareStrategyPropertyAction.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2000, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.ICompareStrategy; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; + +/** + * Toggles the activation of a compare strategy + */ +public class ChangeCompareStrategyPropertyAction extends Action implements IPropertyChangeListener, DisposeListener { + + public static final String COMPARE_STRATEGIES = "COMPARE_STRATEGIES"; //$NON-NLS-1$ + public static final String COMPARE_STRATEGY_ACTIONS = "COMPARE_STRATEGY_ACTIONS"; //$NON-NLS-1$ + public static final String COMPARE_STRATEGIES_INITIALIZING = "COMPARE_STRATEGIES_INITIALIZING"; //$NON-NLS-1$ + + private CompareConfiguration fCompareConfiguration; + private ResourceBundle fBundle; + private String fPrefix = "strategy."; //$NON-NLS-1$ + private CompareStrategyDescriptor fCompareStrategyDescriptor; + private ICompareStrategy fCompareStrategy; + + public ChangeCompareStrategyPropertyAction(CompareStrategyDescriptor compareStrategyDescriptor, CompareConfiguration compareConfiguration) { + this.fBundle = compareStrategyDescriptor.getResourceBundle(); + this.fCompareStrategyDescriptor = compareStrategyDescriptor; + this.fCompareStrategy = compareStrategyDescriptor.createCompareStrategy(); + Utilities.initAction(this, fBundle, fPrefix); + + //Utilities only loads images from compare plugin + setImageDescriptor(compareStrategyDescriptor.getImageDescriptor()); + setId(compareStrategyDescriptor.getStrategyId());//TODO this ok? + setCompareConfiguration(compareConfiguration); + + if (fCompareStrategy.isEnabledInitially()) { + setChecked(true); + setProperty(true); + } + } + + void setProperty(boolean state){ + if (fCompareConfiguration != null){ + Map strategies = new HashMap(); + if(fCompareConfiguration.getProperty(COMPARE_STRATEGIES) != null){ + strategies.putAll((Map)fCompareConfiguration.getProperty(COMPARE_STRATEGIES)); + } + if(state){ + strategies.put(fCompareStrategyDescriptor.getStrategyId(), fCompareStrategy); + }else{ + strategies.remove(fCompareStrategyDescriptor.getStrategyId()); + } + fCompareConfiguration.setProperty(COMPARE_STRATEGIES, strategies); + } + } + + boolean getProperty(){ + Map strategies = (Map) fCompareConfiguration.getProperty(COMPARE_STRATEGIES); + if(strategies == null){ + strategies = new HashMap(); + } + return strategies.containsKey(fCompareStrategyDescriptor.getStrategyId()); + } + + public void run() { + boolean b= !getProperty(); + setChecked(b); + setProperty(b); + } + + public void setChecked(boolean state) { + super.setChecked(state); + Utilities.initToggleAction(this, fBundle, fPrefix, state); + } + + public void setCompareConfiguration(CompareConfiguration cc) { + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + fCompareConfiguration= cc; + if (fCompareConfiguration != null) + fCompareConfiguration.addPropertyChangeListener(this); + setChecked(getProperty()); + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(COMPARE_STRATEGIES) && event.getNewValue() instanceof Map) { + setChecked(((Map)event.getNewValue()).containsKey(fCompareStrategyDescriptor.getStrategyId())); + } + } + + public void dispose(){ + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + } + + public void widgetDisposed(DisposeEvent e) { + dispose(); + } + + public String getStrategyId() { + return fCompareStrategyDescriptor.getStrategyId(); + } + + public void setInput(Object input, Object ancestor, Object left, Object right) { + fCompareStrategy.setInput(input, ancestor, left, right); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java index fc427fe23..3eebc2c8f 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2007 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,21 +10,33 @@ *******************************************************************************/ package org.eclipse.compare.internal; +import java.util.Iterator; +import java.util.List; import java.util.ResourceBundle; -import org.eclipse.jface.action.*; - -import org.eclipse.ui.*; +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.NavigationAction; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.help.IWorkbenchHelpSystem; import org.eclipse.ui.part.EditorActionBarContributor; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; -import org.eclipse.compare.*; - public class CompareEditorContributor extends EditorActionBarContributor { + public final static String STRATEGY_SEPARATOR = "compare.strategies"; //$NON-NLS-1$ + public final static String BUILTIN_SEPARATOR = "compare.builtin"; //$NON-NLS-1$ + private IEditorPart fActiveEditorPart= null; private ChangePropertyAction fIgnoreWhitespace; @@ -59,10 +71,11 @@ public class CompareEditorContributor extends EditorActionBarContributor { * @see EditorActionBarContributor#contributeToToolBar(IToolBarManager) */ public void contributeToToolBar(IToolBarManager tbm) { - tbm.add(new Separator()); - tbm.add(fIgnoreWhitespace); - tbm.add(fToolbarNext); - tbm.add(fToolbarPrevious); + tbm.add(new Separator(STRATEGY_SEPARATOR)); + tbm.add(new Separator(BUILTIN_SEPARATOR)); + tbm.appendToGroup(BUILTIN_SEPARATOR, fIgnoreWhitespace); + tbm.appendToGroup(BUILTIN_SEPARATOR, fToolbarNext); + tbm.appendToGroup(BUILTIN_SEPARATOR, fToolbarPrevious); } /* @@ -106,6 +119,34 @@ public class CompareEditorContributor extends EditorActionBarContributor { CompareConfiguration cc= editor.getCompareConfiguration(); fIgnoreWhitespace.setCompareConfiguration(cc); + + IContributionItem[] items = actionBars.getToolBarManager().getItems(); + boolean inStrategies = false; + for (int i=0;i<items.length;i++) { + if (items[i].getId().equals(STRATEGY_SEPARATOR)) { //$NON-NLS-1$ + inStrategies = true; + } else if (items[i].getId().equals(BUILTIN_SEPARATOR)) { //$NON-NLS-1$ + break; + } else if (inStrategies) { + actionBars.getToolBarManager().remove(items[i]); + } + } + + IEditorInput input = editor.getEditorInput(); + if (input instanceof CompareEditorInput) { + Object strategyActions = ((CompareEditorInput)input).getCompareConfiguration().getProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGY_ACTIONS); + if (strategyActions instanceof List && !((List)strategyActions).isEmpty()) { + Iterator i = ((List)strategyActions).iterator(); + while (i.hasNext()) { + Object next = i.next(); + if (next instanceof ChangeCompareStrategyPropertyAction) + actionBars.getToolBarManager().appendToGroup(STRATEGY_SEPARATOR, (ChangeCompareStrategyPropertyAction)next); + } + actionBars.getToolBarManager().markDirty(); + actionBars.getToolBarManager().update(true); + actionBars.updateActionBars(); + } + } } else { IActionBars actionBars= getActionBars(); actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), null); diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStrategyDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStrategyDescriptor.java new file mode 100644 index 000000000..e6bc2647e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStrategyDescriptor.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.net.URL; +import java.util.Enumeration; +import java.util.ResourceBundle; + +import org.eclipse.compare.ICompareStrategy; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.resource.ImageDescriptor; +import org.osgi.framework.Bundle; + +/** + * Describes compare strategy extension. + */ +public class CompareStrategyDescriptor { + + private final static String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$ + private final static String ID_ATTRIBUTE = "id"; //$NON-NLS-1$ + + private IConfigurationElement fConfiguration; + private ResourceBundle fResourceBundle; + private ImageDescriptor fImageDescriptor; + + private class ConfigurationKeysEnumeration implements Enumeration { + + private String[] keySet; + private int cursor = 0; + + public ConfigurationKeysEnumeration(IConfigurationElement configuration) { + super(); + this.keySet = configuration.getAttributeNames(); + } + + public boolean hasMoreElements() { + return cursor >= keySet.length; + } + + public Object nextElement() { + return keySet[cursor++]; + } + + } + + public CompareStrategyDescriptor(IConfigurationElement config) { + fConfiguration = config; + fResourceBundle = new ResourceBundle() { + protected Object handleGetObject(String key) { + return fConfiguration.getAttribute(key); + } + + public Enumeration getKeys() { + return new ConfigurationKeysEnumeration(fConfiguration); + } + }; + + URL url = null; + String pluginId= fConfiguration.getContributor().getName(); + Bundle bundle= Platform.getBundle(pluginId); + if (bundle != null) { + String path = Utilities.getString(fResourceBundle, "strategy.image", "strategy.image"); + if (path!=null) + url= FileLocator.find(bundle, new Path(path), null); + } + fImageDescriptor = (url==null)?null:ImageDescriptor.createFromURL(url); + } + + public ICompareStrategy createCompareStrategy() { + try { + return (ICompareStrategy) fConfiguration + .createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + return null; + } + + public String getStrategyId() { + return fConfiguration.getAttribute(ID_ATTRIBUTE); + } + + public ResourceBundle getResourceBundle() { + return fResourceBundle; + } + + public ImageDescriptor getImageDescriptor() { + return fImageDescriptor; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java index e1d7e210b..194b00932 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -16,6 +16,9 @@ import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -195,9 +198,12 @@ public final class CompareUIPlugin extends AbstractUIPlugin { private static final String STRUCTURE_CREATOR_ID_ATTRIBUTE= "structureCreatorId"; //$NON-NLS-1$ private static final String VIEWER_TAG= "viewer"; //$NON-NLS-1$ + private static final String STRATEGY_TAG = "strategy"; //$NON-NLS-1$ private static final String STRUCTURE_MERGE_VIEWER_EXTENSION_POINT= "structureMergeViewers"; //$NON-NLS-1$ private static final String STRUCTURE_MERGE_VIEWER_ID_ATTRIBUTE= "structureMergeViewerId"; //$NON-NLS-1$ private static final String CONTENT_MERGE_VIEWER_EXTENSION_POINT= "contentMergeViewers"; //$NON-NLS-1$ + private static final String COMPARE_STRATEGIES_EXTENTION_POINT = "compareStrategies"; //$NON-NLS-1$ + private static final String COMPARE_STRATEGY_ID_ATTRIBUTE = "strategyId"; //$NON-NLS-1$ private static final String CONTENT_MERGE_VIEWER_ID_ATTRIBUTE= "contentMergeViewerId"; //$NON-NLS-1$ private static final String CONTENT_VIEWER_EXTENSION_POINT= "contentViewers"; //$NON-NLS-1$ private static final String CONTENT_VIEWER_ID_ATTRIBUTE= "contentViewerId"; //$NON-NLS-1$ @@ -236,6 +242,7 @@ public final class CompareUIPlugin extends AbstractUIPlugin { private CompareRegistry fStructureMergeViewers= new CompareRegistry(); private CompareRegistry fContentViewers= new CompareRegistry(); private CompareRegistry fContentMergeViewers= new CompareRegistry(); + private CompareRegistry fCompareStrategies = new CompareRegistry(); private Map fStructureViewerAliases; private CompareFilter fFilter; @@ -391,7 +398,29 @@ public final class CompareUIPlugin extends AbstractUIPlugin { if (CONTENT_TYPE_BINDING.equals(element.getName())) fContentMergeViewers.createBinding(element, CONTENT_MERGE_VIEWER_ID_ATTRIBUTE); } - + + // collect all extensions that define the compare strategy extension point + elements = registry.getConfigurationElementsFor(PLUGIN_ID, + COMPARE_STRATEGIES_EXTENTION_POINT); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!STRATEGY_TAG.equals(name)) + logErrorMessage(Utilities + .getFormattedString( + "CompareUIPlugin.unexpectedTag", name, STRATEGY_TAG)); //$NON-NLS-1$ + fCompareStrategies.register(element, + new CompareStrategyDescriptor(element)); + } + } + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fCompareStrategies.createBinding(element, + COMPARE_STRATEGY_ID_ATTRIBUTE); + } + // collect all viewers which define the content viewer extension point elements= registry.getConfigurationElementsFor(PLUGIN_ID, CONTENT_VIEWER_EXTENSION_POINT); for (int i= 0; i < elements.length; i++) { @@ -850,6 +879,113 @@ public final class CompareUIPlugin extends AbstractUIPlugin { return getViewer(descriptors[0], oldViewer, parent, configuration); } + public CompareStrategyDescriptor[] findCompareStrategies(Object in) { + Collection contentTypes = getContentTypes(in); + if (contentTypes == null) { + return new CompareStrategyDescriptor[0]; + } + Set result = new LinkedHashSet(); + Iterator ctIterator = contentTypes.iterator(); + while (ctIterator.hasNext()) { + Object ct = ctIterator.next(); + if (ct instanceof IContentType) { + List list = fCompareStrategies.searchAll((IContentType) ct); + if (list != null) + result.addAll(list); + } else if (ct instanceof String) { + List list = fCompareStrategies.searchAll((String) ct); + if (list != null) + result.addAll(list); + } + } + + ArrayList list = new ArrayList(result); + Collections.sort(list, new Comparator(){ + public int compare(Object left, Object right) { + return ((CompareStrategyDescriptor)left).getStrategyId().compareTo( + ((CompareStrategyDescriptor)right).getStrategyId()); + } + }); + + + return (CompareStrategyDescriptor[]) result + .toArray(new CompareStrategyDescriptor[result.size()]); + } + + private Collection getContentTypes(Object in) { + Set result = new LinkedHashSet(); + if (in instanceof IStreamContentAccessor) { + String type = ITypedElement.TEXT_TYPE; + + if (in instanceof ITypedElement) { + ITypedElement tin = (ITypedElement) in; + + IContentType ct = getContentType(tin); + if (ct != null) { + result.add(ct); + } + + String ty = tin.getType(); + if (ty != null) + type = ty; + result.add(type); + } + return result; + } + + if (!(in instanceof ICompareInput)) + return null; + + ICompareInput input = (ICompareInput) in; + + IContentType ctype = getCommonType(input); + if (ctype != null) { + result.add(ctype); + } + + String[] types = getTypes(input); + String type = null; + if (isHomogenous(types)) + type = types[0]; + + if (ITypedElement.FOLDER_TYPE.equals(type)) + return null; + + if (type == null) { + int n = 0; + for (int i = 0; i < types.length; i++) + if (!ITypedElement.UNKNOWN_TYPE.equals(types[i])) { + n++; + if (type == null) + type = types[i]; // remember the first known type + } + if (n > 1) // don't use the type if there were more than one + type = null; + } + + if (type != null) { + result.add(type); + } + + // fallback + String leftType = guessType(input.getLeft()); + String rightType = guessType(input.getRight()); + + if (leftType != null || rightType != null) { + boolean right_text = rightType != null + && ITypedElement.TEXT_TYPE.equals(rightType); + boolean left_text = leftType != null + && ITypedElement.TEXT_TYPE.equals(leftType); + if ((rightType != null && !right_text) + || (leftType != null && !left_text)) { + result.add(BINARY_TYPE); + } + result.add(ITypedElement.TEXT_TYPE); + + } + return result; + } + public ViewerDescriptor[] findContentViewerDescriptor(Viewer oldViewer, Object in, CompareConfiguration cc) { Set result = new LinkedHashSet(); if (in instanceof IStreamContentAccessor) { diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java index 8686bbbb3..767fe3b05 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,9 +10,13 @@ *******************************************************************************/ package org.eclipse.compare.internal; -import org.eclipse.jface.text.*; +import org.eclipse.compare.ICompareStrategy; import org.eclipse.compare.contentmergeviewer.ITokenComparator; import org.eclipse.compare.rangedifferencer.IRangeComparator; +import org.eclipse.core.internal.expressions.util.LRUCache; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; /** * Implements the <code>IRangeComparator</code> interface for lines in a document. @@ -29,6 +33,9 @@ public class DocLineComparator implements ITokenComparator { private int fLineCount; private int fLength; private boolean fIgnoreWhiteSpace; + private ICompareStrategy[] fCompareStrategies; + private char fContributor; + private LRUCache fCompareStrategyCache; /** * Creates a <code>DocLineComparator</code> for the given document range. @@ -40,9 +47,23 @@ public class DocLineComparator implements ITokenComparator { * @param ignoreWhiteSpace if <code>true</code> white space is ignored when comparing lines */ public DocLineComparator(IDocument document, IRegion region, - boolean ignoreWhiteSpace) { + boolean ignoreWhiteSpace, ICompareStrategy[] compareStrategies, char contributor) { fDocument = document; fIgnoreWhiteSpace = ignoreWhiteSpace; + fCompareStrategies = compareStrategies; + fContributor = contributor; + + boolean cacheIgnoredRegions = false; + if (compareStrategies!=null && compareStrategies.length>0) { + cacheIgnoredRegions = true; + for (int i=0;i<compareStrategies.length;i++) { + if (!compareStrategies[i].areIgnoredRegionsReusable()) { + cacheIgnoredRegions = false; + break; + } + } + } + fCompareStrategyCache = (cacheIgnoredRegions)?new LRUCache(1024):null; fLineOffset = 0; if (region != null) { @@ -116,18 +137,15 @@ public class DocLineComparator implements ITokenComparator { DocLineComparator other= (DocLineComparator) otherComparator; if (fIgnoreWhiteSpace) { - String s1= extract(thisIndex); - String s2= other.extract(otherIndex); - //return s1.trim().equals(s2.trim()); - return compare(s1, s2); + String[] linesToCompare = extract(thisIndex, otherIndex, other); + return compare(linesToCompare[0], linesToCompare[1]); } int tlen= getTokenLength(thisIndex); int olen= other.getTokenLength(otherIndex); - if (tlen == olen) { - String s1= extract(thisIndex); - String s2= other.extract(otherIndex); - return s1.equals(s2); + if (tlen == olen || fCompareStrategies!=null) { + String[] linesToCompare = extract(thisIndex, otherIndex, other); + return linesToCompare[0].equals(linesToCompare[1]); } } return false; @@ -149,6 +167,39 @@ public class DocLineComparator implements ITokenComparator { //---- private methods + private String[] extract(int thisIndex, int otherIndex, DocLineComparator other){ + + String[] extracts = new String[2]; + if (fCompareStrategies!=null || fCompareStrategies.length>0) { + if (fCompareStrategyCache!=null) { + extracts[0] = (String)fCompareStrategyCache.get(new Integer(thisIndex)); + if (extracts[0] == null) { + extracts[0] = Utilities.applyCompareStrategies( + extract(thisIndex), fContributor, other.extract(otherIndex), other.fContributor, fCompareStrategies); + fCompareStrategyCache.put(new Integer(thisIndex), extracts[0]); + } + + extracts[1] = (String)other.fCompareStrategyCache.get(new Integer(otherIndex)); + if (extracts[1] == null) { + extracts[1] = Utilities.applyCompareStrategies( + other.extract(otherIndex), other.fContributor, extract(thisIndex), fContributor, fCompareStrategies); + other.fCompareStrategyCache.put(new Integer(otherIndex), extracts[1]); + } + } else { + String thisLine = extract(thisIndex); + String otherLine = other.extract(otherIndex); + extracts = new String[] { + Utilities.applyCompareStrategies(thisLine, fContributor, + otherLine, other.fContributor, fCompareStrategies), + Utilities.applyCompareStrategies(otherLine, other.fContributor, + thisLine, fContributor, fCompareStrategies)}; + } + } else { + extracts = new String[]{extract(thisIndex), other.extract(otherIndex)}; + } + return extracts; + } + /** * Extract a single line from the underlying document without the line separator. * diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java index ec99bd716..549abf3c0 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -990,6 +990,10 @@ public class MergeSourceViewer implements ISelectionChangedListener, public void addTextAction(IAction textEditorPropertyAction) { textActions.add(textEditorPropertyAction); } + + public boolean removeTextAction(IAction textEditorPropertyAction) { + return textActions.remove(textEditorPropertyAction); + } public void addAction(String id, IAction action) { fActions.put(id, action); diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java index fe48c7219..6ba04b3c1 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -31,6 +31,7 @@ import java.util.ResourceBundle; import org.eclipse.compare.CompareConfiguration; import org.eclipse.compare.CompareUI; +import org.eclipse.compare.ICompareStrategy; import org.eclipse.compare.IEncodedStreamContentAccessor; import org.eclipse.compare.ISharedDocumentAdapter; import org.eclipse.compare.IStreamContentAccessor; @@ -71,6 +72,7 @@ import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; @@ -140,6 +142,27 @@ public class Utilities { return dflt; } + /** + * Returns the active compare strategies for the compare configuration + * @param cc + * @return the active compare strategies + */ + public static ICompareStrategy[] getCompareStrategies( + CompareConfiguration cc) { + if (cc != null) { + Object value = cc + .getProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES); + if (value instanceof Map) { + Map strategiesMap = (Map) value; + return (ICompareStrategy[]) strategiesMap.values() + .toArray( + new ICompareStrategy[strategiesMap + .size()]); + } + } + return new ICompareStrategy[0]; + } + public static void firePropertyChange(ListenerList listenerList, Object source, String property, Object old, Object newValue) { PropertyChangeEvent event= new PropertyChangeEvent(source, property, old, newValue); firePropertyChange(listenerList, event); @@ -912,4 +935,46 @@ public class Utilities { }); return result[0]; } + + /** + * Applies the compare strategies to the lines of text taken from the specified contributors + * @param line1 + * @param line1Contributor + * @param line2 + * @param line2Contributor + * @param strategies may be null + * @return returns the result of applying the strategies to the line from the contributor + */ + public static String applyCompareStrategies(String line1, char line1Contributor, + String line2, char line2Contributor, ICompareStrategy[] strategies) { + IRegion[][] ignoredRegions = new IRegion[strategies.length][]; + + HashMap input = new HashMap(4); + input.put(ICompareStrategy.THIS_LINE, line1); + input.put(ICompareStrategy.THIS_CONTRIBUTOR, new Character(line1Contributor)); + input.put(ICompareStrategy.OTHER_LINE, line2); + input.put(ICompareStrategy.OTHER_CONTRIBUTOR, new Character(line2Contributor)); + for (int i=0;i<strategies.length;i++) { + ignoredRegions[i] = strategies[i].ignoreRegions(input); + } + + boolean[] ignored = new boolean[line1.length()]; + for (int j=0;j<ignoredRegions.length;j++) { + if (ignoredRegions[j]!=null) { + for (int k=0;k<ignoredRegions[j].length;k++) { + if (ignoredRegions[j][k]!=null) + for (int l=0;l<ignoredRegions[j][k].getLength();l++) { + ignored[ignoredRegions[j][k].getOffset()+l]=true; + } + } + } + } + StringBuffer buffer = new StringBuffer(line1.length()); + for (int i=0;i<ignored.length;i++) { + if (!ignored[i]) { + buffer.append(line1.charAt(i)); + } + } + return buffer.toString(); + } } diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java index 81a28fe4d..9e013e60a 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2012 IBM Corporation and others. + * Copyright (c) 2007, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -37,6 +37,7 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.IProgressService; import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.ICompareStrategy; import org.eclipse.compare.contentmergeviewer.ITokenComparator; import org.eclipse.compare.internal.CompareContentViewerSwitchingPane; import org.eclipse.compare.internal.CompareMessages; @@ -422,12 +423,13 @@ public class DocumentMerger { resetPositions(aDoc); boolean ignoreWhiteSpace= isIgnoreWhitespace(); + ICompareStrategy[] compareStrategies = getCompareStrategies(); - DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); - DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace, compareStrategies, MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace, compareStrategies,MergeViewerContentProvider.LEFT_CONTRIBUTOR); DocLineComparator sancestor= null; if (aDoc != null) { - sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace, compareStrategies, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); /*if (isPatchHunk()) { if (isHunkOnLeft()) { sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); @@ -594,12 +596,13 @@ public class DocumentMerger { aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); boolean ignoreWhiteSpace= isIgnoreWhitespace(); - - DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); - DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + ICompareStrategy[] compareStrategies = getCompareStrategies(); + + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace, compareStrategies, MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace, compareStrategies, MergeViewerContentProvider.LEFT_CONTRIBUTOR); DocLineComparator sancestor= null; if (aDoc != null) - sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace, compareStrategies, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); final Object[] result= new Object[1]; final DocLineComparator sa= sancestor, sl= sleft, sr= sright; @@ -682,6 +685,10 @@ public class DocumentMerger { return Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); } + private ICompareStrategy[] getCompareStrategies() { + return Utilities.getCompareStrategies(getCompareConfiguration()); + } + private boolean isCappingDisabled() { return CompareUIPlugin.getDefault().getPreferenceStore().getBoolean(ComparePreferencePage.CAPPING_DISABLED); } diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java index 60fa61b97..7f3e83a3c 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java @@ -22,6 +22,7 @@ import java.util.Set; import org.eclipse.compare.IStreamContentAccessor; import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.MergeViewerContentProvider; import org.eclipse.compare.internal.Utilities; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; @@ -360,7 +361,7 @@ public class Differencer { description= LEFT | ADDITION; } else { description= CONFLICTING | ADDITION; - if (contentsEqual(left, right)) + if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR)) description|= PSEUDO_CONFLICT; } } @@ -369,20 +370,20 @@ public class Differencer { if (right == null) { description= CONFLICTING | DELETION | PSEUDO_CONFLICT; } else { - if (contentsEqual(ancestor, right)) + if (contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR)) description= LEFT | DELETION; else description= CONFLICTING | CHANGE; } } else { if (right == null) { - if (contentsEqual(ancestor, left)) + if (contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR)) description= RIGHT | DELETION; else description= CONFLICTING | CHANGE; } else { - boolean ay= contentsEqual(ancestor, left); - boolean am= contentsEqual(ancestor, right); + boolean ay= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR); + boolean am= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR); if (ay && am) { // empty @@ -392,7 +393,7 @@ public class Differencer { description= LEFT | CHANGE; } else { description= CONFLICTING | CHANGE; - if (contentsEqual(left, right)) + if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR)) description|= PSEUDO_CONFLICT; } } @@ -410,7 +411,7 @@ public class Differencer { if (right == null) { description= DELETION; } else { - if (! contentsEqual(left, right)) + if (! contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR)) description= CHANGE; } } @@ -418,6 +419,26 @@ public class Differencer { return description; } + + /** + * Performs a content compare on the two given inputs. + * <p> + * The <code>Differencer</code> implementation returns + * <code>contentsEqual(Object input1, Object input2)</code>. + * Subclasses may override to implement a different content compare + * on the given inputs. + * </p> + * + * @param input1 first input to contents compare + * @param contributor1 the contributor of input1 + * @param input2 second input to contents compare + * @param contributor2 the contributor of input2 + * @return <code>true</code> if content is equal + * @since 3.6 + */ + protected boolean contentsEqual(Object input1, char contributor1, Object input2, char contributor2) { + return contentsEqual(input1, input2); + } /** * Performs a content compare on the two given inputs. diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator3.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator3.java new file mode 100644 index 000000000..db450bfd3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator3.java @@ -0,0 +1,46 @@ +package org.eclipse.compare.structuremergeviewer; + +import org.eclipse.compare.ICompareStrategy; + +/******************************************************************************* + * Copyright (c) 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + + /** + * An extension to the {@link IStructureCreator2} interface that supports the + * use of activated compare strategies when generating the string contents + * of a node. + * <p> + * Clients may implement this interface; there is no standard implementation. + * </p> + * @since 3.6 + */ + public interface IStructureCreator3 extends IStructureCreator2 { + + /** + * Returns true if the two nodes are equal for comparison purposes. + * If <code>compareStrategies</code> is not empty, the strategies should be applied + * to each line of each node's text representation. The strategies should be applied to each + * line individually, without delimiters, but with all other whitespace characters intact. + * A default implementation <code>StructureCreator.contentsEquals()</code> is available for reuse. + * @param node1 + * @param contributor1 either 'A', 'L', or 'R' for ancestor, left or right contributor + * @param node2 + * @param contributor2 either 'A', 'L', or 'R' for ancestor, left or right contributor + * @param ignoreWhitespace if <code>true</code> whitespace characters should be ignored + * when determining equality. + * @param compareStrategies the strategies used to customize the comparison of lines of text. + * @return whether the two nodes are equal for comparison purposes + */ + boolean contentsEquals(Object node1, char contributor1, + Object node2, char contributor2, boolean ignoreWhitespace, + ICompareStrategy[] compareStrategies); + + }
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java index 799afba98..db6233bec 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2009 IBM Corporation and others. + * Copyright (c) 2006, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,9 +10,13 @@ *******************************************************************************/ package org.eclipse.compare.structuremergeviewer; +import java.io.BufferedReader; +import java.io.StringReader; import java.io.UnsupportedEncodingException; +import java.util.List; import org.eclipse.compare.CompareUI; +import org.eclipse.compare.ICompareStrategy; import org.eclipse.compare.IEditableContent; import org.eclipse.compare.IEncodedStreamContentAccessor; import org.eclipse.compare.ISharedDocumentAdapter; @@ -22,6 +26,7 @@ import org.eclipse.compare.SharedDocumentAdapter; import org.eclipse.compare.contentmergeviewer.IDocumentRange; import org.eclipse.compare.internal.CompareUIPlugin; import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.patch.LineReader; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; @@ -430,4 +435,88 @@ public abstract class StructureCreator implements IStructureCreator2 { } return null; } + + /** + * Applies the compare strategies to the line of text taken from the specified contributor + * @param line1 + * @param contributor1 + * @param line2 + * @param contributor2 + * @param strategies may be null + * @return returns the result of applying the strategies to the line from the contributor + * @since 3.6 + */ + public String applyCompareStrategies(String line1, char contributor1, + String line2, char contributor2, ICompareStrategy[] strategies) { + return Utilities.applyCompareStrategies(line1, contributor1, line2, contributor2, strategies); + } + + /** + * Reusable implementation of <code>IStructureCreator3.contentsEquals()</code> + * @param node1 + * @param contributor1 + * @param node2 + * @param contributor2 + * @param ignoreWhitespace + * @param compareStrategies + * @return returns whether the nodes are equal for comparison purposes + * @since 3.6 + */ + public boolean contentsEquals(Object node1, char contributor1, + Object node2, char contributor2, boolean ignoreWhitespace, + ICompareStrategy[] compareStrategies) { + + List lines1 = LineReader.readLines(new BufferedReader(new StringReader(getContents(node1, false)))); + List lines2 = LineReader.readLines(new BufferedReader(new StringReader(getContents(node2, false)))); + + if (lines1.size()!=lines2.size()) + return false; + + for (int i=0;i<lines1.size();i++) { + String s1 = (String)lines1.get(i); + String s2 = (String)lines2.get(i); + + if (compareStrategies != null && compareStrategies.length>0) { + s1 = applyCompareStrategies(s1, contributor1, + s2, contributor2, compareStrategies); + s2 = applyCompareStrategies(s2, contributor2, + s1, contributor1, compareStrategies); + } + + if (ignoreWhitespace) { + int l1= s1.length(); + int l2= s2.length(); + int c1= 0, c2= 0; + int i1= 0, i2= 0; + + while (c1 != -1) { + + c1= -1; + while (i1 < l1) { + char c= s1.charAt(i1++); + if (! Character.isWhitespace(c)) { + c1= c; + break; + } + } + + c2= -1; + while (i2 < l2) { + char c= s2.charAt(i2++); + if (! Character.isWhitespace(c)) { + c2= c; + break; + } + } + + if (c1 != c2) + return false; + } + } else if (!s1.equals(s2)) { + return false; + } + } + + return true; + } } diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java index 4b8700659..dc6e9eb1c 100644 --- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -475,8 +475,8 @@ public class StructureDiffViewer extends DiffTreeViewer { if (fDifferencer == null) fDifferencer= new Differencer() { - protected boolean contentsEqual(Object o1, Object o2) { - return StructureDiffViewer.this.contentsEqual(o1, o2); + protected boolean contentsEqual(Object o1, char contributor1, Object o2, char contributor2) { + return StructureDiffViewer.this.contentsEqual(o1, contributor1, o2, contributor2); } protected Object visit(Object data, int result, Object ancestor, Object left, Object right) { Object o= super.visit(data, result, ancestor, left, right); @@ -608,11 +608,17 @@ public class StructureDiffViewer extends DiffTreeViewer { * Called from the difference engine. * Returns <code>null</code> if no structure creator has been set. */ - private boolean contentsEqual(Object o1, Object o2) { + private boolean contentsEqual(Object o1, char contributor1, Object o2, char contributor2) { if (fStructureCreator != null) { - boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); - String s1= fStructureCreator.getContents(o1, ignoreWhiteSpace); - String s2= fStructureCreator.getContents(o2, ignoreWhiteSpace); + boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); + ICompareStrategy[] compareStrategies = Utilities.getCompareStrategies(getCompareConfiguration()); + String s1, s2; + if (fStructureCreator instanceof IStructureCreator3) { + return ((IStructureCreator3)fStructureCreator).contentsEquals(o1, contributor1, o2, contributor2, ignoreWhiteSpace, compareStrategies); + } else { + s1= fStructureCreator.getContents(o1, ignoreWhiteSpace); + s2= fStructureCreator.getContents(o2, ignoreWhiteSpace); + } if (s1 == null || s2 == null) return false; return s1.equals(s2); @@ -630,6 +636,9 @@ public class StructureDiffViewer extends DiffTreeViewer { String key= event.getProperty(); if (key.equals(CompareConfiguration.IGNORE_WHITESPACE)) { diff(); + } else if (key.equals(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES) && + getCompareConfiguration().getProperty(ChangeCompareStrategyPropertyAction.COMPARE_STRATEGIES_INITIALIZING)==null) { + diff(); } else if (key.equals("ANCESTOR_STRUCTURE_REFRESH")) { //$NON-NLS-1$ fAncestorStructure.refresh(new NullProgressMonitor()); diff(); diff --git a/bundles/org.eclipse.compare/plugin.xml b/bundles/org.eclipse.compare/plugin.xml index 751f46aef..266072dd3 100644 --- a/bundles/org.eclipse.compare/plugin.xml +++ b/bundles/org.eclipse.compare/plugin.xml @@ -19,6 +19,7 @@ <extension-point id="structureMergeViewers" name="%structureMergeViewers" schema="schema/structureMergeViewers.exsd"/> <extension-point id="contentMergeViewers" name="%contentMergeViewers" schema="schema/contentMergeViewers.exsd"/> <extension-point id="contentViewers" name="%contentViewers" schema="schema/contentViewers.exsd"/> + <extension-point id="compareStrategies" name="%compareStrategies" schema="schema/compareStrategies.exsd"/> <!-- Extensions --> <extension diff --git a/bundles/org.eclipse.compare/schema/compareStrategies.exsd b/bundles/org.eclipse.compare/schema/compareStrategies.exsd new file mode 100644 index 000000000..56c4658ba --- /dev/null +++ b/bundles/org.eclipse.compare/schema/compareStrategies.exsd @@ -0,0 +1,228 @@ +<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.compare" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appInfo>
+ <meta.schema plugin="org.eclipse.compare" id="compareStrategies" name="Compare Strategies"/>
+ </appInfo>
+ <documentation>
+ This extension point allows a plug-in to register a compare strategy
+for specific content types. The compare strategy will be exposed as a
+toggle action in the compare viewer and can be used to customize how
+differences are calculated when comparing text. The extension point must
+implement the interface <samp>org.eclipse.compare.ICompareStrategy</samp>.
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appInfo>
+ <meta.element />
+ </appInfo>
+ </annotation>
+ <complexType>
+ <sequence>
+ <element ref="strategy" minOccurs="0" maxOccurs="unbounded"/>
+ <element ref="contentTypeBinding" minOccurs="0" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+ a fully qualified identifier of the target extension point
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+ an optional identifier of the extension instance
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+ an optional name of the extension instance
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="strategy">
+ <complexType>
+ <attribute name="id" type="string" use="required">
+ <annotation>
+ <documentation>
+ a unique identifier that can be used to reference the strategy
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="extensions" type="string">
+ <annotation>
+ <documentation>
+ a comma separated list of file extensions e.g. "java, txt"
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="java" basedOn=":org.eclipse.compare.ICompareStrategy"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.label" type="string" use="required">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.tooltip" type="string" use="required">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.tooltip.checked" type="string">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.tooltip.unchecked" type="string">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.description" type="string" use="required">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.description.checked" type="string">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.description.unchecked" type="string">
+ <annotation>
+ <documentation>
+ A translatable label that will be used in the UI for this strategy.
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategy.image" type="string">
+ <annotation>
+ <documentation>
+ An image that will be used in the UI for this strategy.
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="contentTypeBinding">
+ <annotation>
+ <documentation>
+ A <code>contentTypeBinding</code> binds a compare strategy to a content type.
+ </documentation>
+ </annotation>
+ <complexType>
+ <attribute name="contentTypeId" type="string" use="required">
+ <annotation>
+ <documentation>
+ The id of a content type defined using the <code>org.eclipse.core.contenttype.contentTypes</code> extension point.
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="strategyId" type="string" use="required">
+ <annotation>
+ <documentation>
+ The id of a strategy defined using the <code>strategy</code> element of this extension point (i.e. <code>org.eclipse.compare.compareStrategies</code>)
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="identifier" basedOn="org.eclipse.compare.compareStrategies/strategy/@id"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="since"/>
+ </appInfo>
+ <documentation>
+ [Enter the first release in which this extension point appears.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="examples"/>
+ </appInfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="apiinfo"/>
+ </appInfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="implementation"/>
+ </appInfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+
+</schema>
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RangeDifferenceComparator.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RangeDifferenceComparator.java index 63839174d..09046fc2a 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RangeDifferenceComparator.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RangeDifferenceComparator.java @@ -13,8 +13,8 @@ package org.eclipse.team.internal.ui.synchronize; import java.io.IOException;
import java.io.InputStream;
-import org.eclipse.compare.internal.DocLineComparator;
-import org.eclipse.compare.internal.Utilities;
+import org.eclipse.compare.ICompareStrategy;
+import org.eclipse.compare.internal.*;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.compare.rangedifferencer.RangeDifferencer;
import org.eclipse.core.resources.ResourcesPlugin;
@@ -62,9 +62,9 @@ public abstract class RangeDifferenceComparator extends IDocument lDoc = new Document(left);
IDocument rDoc = new Document(right);
DocLineComparator sleft = new DocLineComparator(lDoc, new Region(0,
- lDoc.getLength()), shouldIgnoreWhitespace());
+ lDoc.getLength()), shouldIgnoreWhitespace(), new ICompareStrategy[0], MergeViewerContentProvider.LEFT_CONTRIBUTOR);
DocLineComparator sright = new DocLineComparator(rDoc, new Region(0,
- rDoc.getLength()), shouldIgnoreWhitespace());
+ rDoc.getLength()), shouldIgnoreWhitespace(), new ICompareStrategy[0], MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
final DocLineComparator sl = sleft, sr = sright;
RangeDifference[] ranges = RangeDifferencer.findRanges(monitor, sl, sr);
return compareRangeDifferences(ranges, lDoc, rDoc);
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RegexDiffComparator.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RegexDiffComparator.java index 333b9afa1..d354d4f08 100644 --- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RegexDiffComparator.java +++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/synchronize/RegexDiffComparator.java @@ -12,7 +12,9 @@ package org.eclipse.team.internal.ui.synchronize; import java.util.regex.Pattern;
+import org.eclipse.compare.ICompareStrategy;
import org.eclipse.compare.internal.DocLineComparator;
+import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.jface.text.*;
@@ -41,9 +43,9 @@ public class RegexDiffComparator extends RangeDifferenceComparator { continue;
DocLineComparator sleft = new DocLineComparator(lDoc, null,
- shouldIgnoreWhitespace());
+ shouldIgnoreWhitespace(), new ICompareStrategy[0], MergeViewerContentProvider.LEFT_CONTRIBUTOR);
DocLineComparator sright = new DocLineComparator(rDoc, null,
- shouldIgnoreWhitespace());
+ shouldIgnoreWhitespace(), new ICompareStrategy[0], MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
IRegion lRegion = lDoc.getLineInformation(diff.leftStart());
int leftEnd = sleft.getTokenStart(diff.leftStart()
|