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: - CoreException – if the content can't be accessed
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> > 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> > 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: - CoreException – if the preview can't be created
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;
}
}