Copyright (c) 2005, 2015 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 Ed Swartz - (bug 157203: [ltk] [patch] TextEditBasedChange/TextChange provides incorrect diff when one side is empty)
/******************************************************************************* * Copyright (c) 2005, 2015 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 * Ed Swartz <ed.swartz@nokia.com> - * (bug 157203: [ltk] [patch] TextEditBasedChange/TextChange provides incorrect diff when one side is empty) *******************************************************************************/
package org.eclipse.ltk.core.refactoring; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditCopier; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.text.edits.TextEditProcessor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.ltk.internal.core.refactoring.Changes;
An abstract base implementation of a change which is based on text edits.
Since:3.2
/** * An abstract base implementation of a change which is based on text edits. * * @since 3.2 */
public abstract class TextEditBasedChange extends Change {
Text edit processor which has the ability to selectively include or exclude single text edits.
/** * Text edit processor which has the ability to selectively include or exclude single text edits. */
static final class LocalTextEditProcessor extends TextEditProcessor { public static final int EXCLUDE= 1; public static final int INCLUDE= 2; private TextEdit[] fExcludes; private TextEdit[] fIncludes; protected LocalTextEditProcessor(IDocument document, TextEdit root, int flags) { super(document, root, flags); } public void setIncludes(TextEdit[] includes) { Assert.isNotNull(includes); Assert.isTrue(fExcludes == null); fIncludes= flatten(includes); } public void setExcludes(TextEdit[] excludes) { Assert.isNotNull(excludes); Assert.isTrue(fIncludes == null); fExcludes= flatten(excludes); } @Override protected boolean considerEdit(TextEdit edit) { if (fExcludes != null) { for (TextEdit fExclude : fExcludes) { if (edit.equals(fExclude)) { return false; } } return true; } if (fIncludes != null) { for (TextEdit fInclude : fIncludes) { if (edit.equals(fInclude)) { return true; } } return false; } return true; } private TextEdit[] flatten(TextEdit[] edits) { List<TextEdit> result= new ArrayList<>(5); for (TextEdit edit : edits) { flatten(result, edit); } return result.toArray(new TextEdit[result.size()]); } private void flatten(List<TextEdit> result, TextEdit edit) { result.add(edit); TextEdit[] children= edit.getChildren(); for (TextEdit child : children) { flatten(result, child); } } }
Value objects encapsulating a document with an associated region.
/** * Value objects encapsulating a document with an associated region. */
static final class PreviewAndRegion { public IDocument document; public IRegion region; public PreviewAndRegion(IDocument d, IRegion r) { document= d; region= r; } }
A special object denoting all edits managed by the change. This even includes those edits not managed by a TextEditBasedChangeGroup.
/** * A special object denoting all edits managed by the change. This even * includes those edits not managed by a {@link TextEditBasedChangeGroup}. */
static final TextEditBasedChangeGroup[] ALL_EDITS= new TextEditBasedChangeGroup[0];
The list of change groups
/** The list of change groups */
private List<TextEditBasedChangeGroup> fChangeGroups; private GroupCategorySet fCombiedGroupCategories;
The name of the change
/** The name of the change */
private String fName;
The text type
/** The text type */
private String fTextType;
Should the positions of edits be tracked during change generation?
/** Should the positions of edits be tracked during change generation? */
private boolean fTrackEdits;
Creates a new abstract text edit change with the specified name. The name is a human-readable value that is displayed to users. The name does not need to be unique, but it must not be null.

The text type of this text edit change is set to txt.

Params:
  • name – the name of the text edit change
See Also:
/** * Creates a new abstract text edit change with the specified name. The name is a * human-readable value that is displayed to users. The name does not * need to be unique, but it must not be <code>null</code>. * <p> * The text type of this text edit change is set to <code>txt</code>. * </p> * * @param name the name of the text edit change * * @see #setTextType(String) */
protected TextEditBasedChange(String name) { Assert.isNotNull(name, "Name must not be null"); //$NON-NLS-1$ fChangeGroups= new ArrayList<>(5); fName= name; fTextType= "txt"; //$NON-NLS-1$ }
Adds a text edit change group. The edits managed by the given text edit change group must be part of the change's root edit.
Params:
  • group – the text edit change group to add
/** * Adds a {@link TextEditBasedChangeGroup text edit change group}. * The edits managed by the given text edit change group must be part of * the change's root edit. * * @param group the text edit change group to add */
public void addChangeGroup(TextEditBasedChangeGroup group) { Assert.isTrue(group != null); fChangeGroups.add(group); if (fCombiedGroupCategories != null) { fCombiedGroupCategories= GroupCategorySet.union(fCombiedGroupCategories, group.getGroupCategorySet()); } }
Adds a text edit group. This method is a convenience method for calling change.addChangeGroup(new TextEditBasedChangeGroup(change, group));.
Params:
  • group – the text edit group to add
/** * Adds a {@link TextEditGroup text edit group}. This method is a convenience * method for calling <code>change.addChangeGroup(new * TextEditBasedChangeGroup(change, group));</code>. * * @param group the text edit group to add */
public void addTextEditGroup(TextEditGroup group) { addChangeGroup(new TextEditBasedChangeGroup(this, group)); }
Returns true if the change has one of the given group categories. Otherwise false is returned.
Params:
  • groupCategories – the group categories to check
Returns:whether the change has one of the given group categories
Since:3.2
/** * Returns <code>true</code> if the change has one of the given group * categories. Otherwise <code>false</code> is returned. * * @param groupCategories the group categories to check * * @return whether the change has one of the given group * categories * * @since 3.2 */
public boolean hasOneGroupCategory(List<GroupCategory> groupCategories) { if (fCombiedGroupCategories == null) { fCombiedGroupCategories= GroupCategorySet.NONE; for (TextEditBasedChangeGroup group : fChangeGroups) { fCombiedGroupCategories= GroupCategorySet.union(fCombiedGroupCategories, group.getGroupCategorySet()); } } return fCombiedGroupCategories.containsOneCategory(groupCategories); }
Returns the text edit change groups managed by this buffer change.
Returns:the text edit change groups
/** * Returns the {@link TextEditBasedChangeGroup text edit change groups} managed by this * buffer change. * * @return the text edit change groups */
public final TextEditBasedChangeGroup[] getChangeGroups() { return fChangeGroups.toArray(new TextEditBasedChangeGroup[fChangeGroups.size()]); } String getContent(IDocument document, IRegion region, boolean expandRegionToFullLine, int surroundingLines) throws CoreException { try { if (expandRegionToFullLine) { int startLine= Math.max(document.getLineOfOffset(region.getOffset()) - surroundingLines, 0); int endLine; if (region.getLength() == 0) { // no lines are in the region, so remove one from the context, // or else spurious changes show up that look like deletes from the source if (surroundingLines == 0) { // empty: show nothing return ""; //$NON-NLS-1$ } endLine= Math.min( document.getLineOfOffset(region.getOffset()) + surroundingLines - 1, document.getNumberOfLines() - 1); } else { endLine= Math.min( document.getLineOfOffset(region.getOffset() + region.getLength() - 1) + surroundingLines, document.getNumberOfLines() - 1); } int offset= document.getLineInformation(startLine).getOffset(); IRegion endLineRegion= document.getLineInformation(endLine); int length= endLineRegion.getOffset() + endLineRegion.getLength() - offset; return document.get(offset, length); } else { return document.get(region.getOffset(), region.getLength()); } } catch (BadLocationException e) { throw Changes.asCoreException(e); } }
Returns the current content of the document this text change is associated with.
Params:
  • pm – a progress monitor to report progress or null if no progress reporting is desired
Throws:
Returns:the current content of the text edit change
/** * Returns the current content of the document this text * change is associated with. * * @param pm a progress monitor to report progress or <code>null</code> * if no progress reporting is desired * @return the current content of the text edit change * * @exception CoreException if the content can't be accessed */
public abstract String getCurrentContent(IProgressMonitor pm) throws CoreException;
Returns the current content of the text edit change clipped to a specific region. The region is determined as follows:
  • if expandRegionToFullLine is false then the parameter region determines the clipping.
  • if expandRegionToFullLine is true then the region determined by the parameter region is extended to cover full lines.
  • if surroundingLines > 0 then the given number of surrounding lines is added. The value of surroundingLines is only considered if expandRegionToFullLine is true
Params:
  • region – the starting region for the text to be returned
  • expandRegionToFullLine – if true is passed the region is extended to cover full lines
  • surroundingLines – the number of surrounding lines to be added to the clipping region. Is only considered if expandRegionToFullLine is true
  • pm – a progress monitor to report progress or null if no progress reporting is desired
Throws:
  • CoreException – if an exception occurs while accessing the current content
Returns:the current content of the text edit change clipped to a region determined by the given parameters.
/** * Returns the current content of the text edit change clipped to a specific * region. The region is determined as follows: * <ul> * <li>if <code>expandRegionToFullLine</code> is <code>false</code> * then the parameter <code>region</code> determines the clipping. * </li> * <li>if <code>expandRegionToFullLine</code> is <code>true</code> * then the region determined by the parameter <code>region</code> * is extended to cover full lines. * </li> * <li>if <code>surroundingLines</code> &gt; 0 then the given number * of surrounding lines is added. The value of <code>surroundingLines * </code> is only considered if <code>expandRegionToFullLine</code> * is <code>true</code> * </li> * </ul> * * @param region the starting region for the text to be returned * @param expandRegionToFullLine if <code>true</code> is passed the region * is extended to cover full lines * @param surroundingLines the number of surrounding lines to be added to * the clipping region. Is only considered if <code>expandRegionToFullLine * </code> is <code>true</code> * @param pm a progress monitor to report progress or <code>null</code> * if no progress reporting is desired * * @return the current content of the text edit change clipped to a region * determined by the given parameters. * * @throws CoreException if an exception occurs while accessing the current content */
public abstract String getCurrentContent(IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException;
Returns whether preview edits are remembered for further region tracking or not.
Returns:true if executed text edits are remembered during preview generation; otherwise false
/** * Returns whether preview edits are remembered for further region * tracking or not. * * @return <code>true</code> if executed text edits are remembered * during preview generation; otherwise <code>false</code> */
public boolean getKeepPreviewEdits() { return fTrackEdits; } @Override public String getName() { return fName; }
Returns a preview of the text edit change clipped to a specific region. The preview is created by applying the text edits managed by the given array of text edit change groups. The region is determined as follows:
  • if expandRegionToFullLine is false then the parameter region determines the clipping.
  • if expandRegionToFullLine is true then the region determined by the parameter region is extended to cover full lines.
  • if surroundingLines > 0 then the given number of surrounding lines is added. The value of surroundingLines is only considered if expandRegionToFullLine is true
Params:
  • changeGroups – a set of change groups for which a preview is to be generated
  • region – the starting region for the clipping
  • expandRegionToFullLine – if true is passed the region is extended to cover full lines
  • surroundingLines – the number of surrounding lines to be added to the clipping region. Is only considered if expandRegionToFullLine is true
  • pm – a progress monitor to report progress or null if no progress reporting is desired
Throws:
  • CoreException – if an exception occurs while generating the preview
See Also:
Returns:the current content of the text change clipped to a region determined by the given parameters.
/** * Returns a preview of the text edit change clipped to a specific region. * The preview is created by applying the text edits managed by the * given array of {@link TextEditBasedChangeGroup text edit change groups}. * The region is determined as follows: * <ul> * <li>if <code>expandRegionToFullLine</code> is <code>false</code> * then the parameter <code>region</code> determines the clipping. * </li> * <li>if <code>expandRegionToFullLine</code> is <code>true</code> * then the region determined by the parameter <code>region</code> * is extended to cover full lines. * </li> * <li>if <code>surroundingLines</code> &gt; 0 then the given number * of surrounding lines is added. The value of <code>surroundingLines * </code> is only considered if <code>expandRegionToFullLine</code> * is <code>true</code> * </li> * </ul> * * @param changeGroups a set of change groups for which a preview is to be * generated * @param region the starting region for the clipping * @param expandRegionToFullLine if <code>true</code> is passed the region * is extended to cover full lines * @param surroundingLines the number of surrounding lines to be added to * the clipping region. Is only considered if <code>expandRegionToFullLine * </code> is <code>true</code> * @param pm a progress monitor to report progress or <code>null</code> * if no progress reporting is desired * * @return the current content of the text change clipped to a region * determined by the given parameters. * * @throws CoreException if an exception occurs while generating the preview * * @see #getCurrentContent(IRegion, boolean, int, IProgressMonitor) */
public abstract String getPreviewContent(TextEditBasedChangeGroup[] changeGroups, IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException;
Returns the preview content as a string.
Params:
  • pm – a progress monitor to report progress or null if no progress reporting is desired
Throws:
Returns:the preview
/** * Returns the preview content as a string. * * @param pm a progress monitor to report progress or <code>null</code> * if no progress reporting is desired * @return the preview * * @throws CoreException if the preview can't be created */
public abstract String getPreviewContent(IProgressMonitor pm) throws CoreException;
Returns the text edit change's text type.
Returns:the text edit change's text type
/** * Returns the text edit change's text type. * * @return the text edit change's text type */
public String getTextType() { return fTextType; } TextEdit[] mapEdits(TextEdit[] edits, TextEditCopier copier) { if (edits == null) return null; final List<TextEdit> result= new ArrayList<>(edits.length); for (TextEdit e : edits) { TextEdit edit= copier.getCopy(e); if (edit != null) result.add(edit); } return result.toArray(new TextEdit[result.size()]); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); for (TextEditBasedChangeGroup element : fChangeGroups) { element.setEnabled(enabled); } }
Controls whether the text edit change should keep executed edits during preview generation.
Params:
  • keep – if true executed preview edits are kept
/** * Controls whether the text edit change should keep executed edits during * preview generation. * * @param keep if <code>true</code> executed preview edits are kept */
public void setKeepPreviewEdits(boolean keep) { fTrackEdits= keep; }
Sets the text type. The text type is used to determine the content merge viewer used to present the difference between the original and the preview content in the user interface. Content merge viewers are defined via the extension point org.eclipse.compare.contentMergeViewers.

The default text type is txt.

Params:
  • type – the text type. If null is passed the text type is reseted to the default text type txt.
/** * Sets the text type. The text type is used to determine the content * merge viewer used to present the difference between the original * and the preview content in the user interface. Content merge viewers * are defined via the extension point <code>org.eclipse.compare.contentMergeViewers</code>. * <p> * The default text type is <code>txt</code>. * </p> * * @param type the text type. If <code>null</code> is passed the text type is * reseted to the default text type <code>txt</code>. */
public void setTextType(String type) { if (type == null) type= "txt"; //$NON-NLS-1$ fTextType= type; } }