Copyright (c) 2000, 2010 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
Anton Leherbauer - [projection] "Backspace" key deleting something else - http://bugs.eclipse.org/301023
/*******************************************************************************
* Copyright (c) 2000, 2010 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
* Anton Leherbauer <anton.leherbauer@windriver.com> - [projection] "Backspace" key deleting something else - http://bugs.eclipse.org/301023
*******************************************************************************/
package org.eclipse.jface.text.projection;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension;
import org.eclipse.jface.text.IDocumentInformationMapping;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextStore;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
A ProjectionDocument
represents a projection of its master
document. The contents of a projection document is a sequence of fragments of
the master document, i.e. the projection document can be thought as being
constructed from the master document by not copying the whole master document
but omitting several ranges of the master document.
The projection document indirectly utilizes its master document as
ITextStore
by means of a ProjectionTextStore
.
The content of a projection document can be changed in two ways. Either by a
text replace applied to the master document or the projection document. Or by
changing the projection between the master document and the projection
document. For the latter the two methods addMasterDocumentRange
and removeMasterDocumentRange
are provided. For any manipulation, the projection document sends out a ProjectionDocumentEvent
describing the change.
Clients are not supposed to directly instantiate this class. In order to obtain a projection document, a ProjectionDocumentManager
should be used. This class is not intended to be subclassed outside of its origin package.
Since: 3.0 @noinstantiate This class is not intended to be instantiated by clients. @noextend This class is not intended to be subclassed by clients.
/**
* A <code>ProjectionDocument</code> represents a projection of its master
* document. The contents of a projection document is a sequence of fragments of
* the master document, i.e. the projection document can be thought as being
* constructed from the master document by not copying the whole master document
* but omitting several ranges of the master document.
* <p>
* The projection document indirectly utilizes its master document as
* <code>ITextStore</code> by means of a <code>ProjectionTextStore</code>.
* <p>
* The content of a projection document can be changed in two ways. Either by a
* text replace applied to the master document or the projection document. Or by
* changing the projection between the master document and the projection
* document. For the latter the two methods <code>addMasterDocumentRange</code>
* and <code>removeMasterDocumentRange</code> are provided. For any
* manipulation, the projection document sends out a
* {@link org.eclipse.jface.text.projection.ProjectionDocumentEvent} describing
* the change.
* <p>
* Clients are not supposed to directly instantiate this class. In order to
* obtain a projection document, a
* {@link org.eclipse.jface.text.projection.ProjectionDocumentManager}should be
* used. This class is not intended to be subclassed outside of its origin
* package.</p>
*
* @since 3.0
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class ProjectionDocument extends AbstractDocument {
Prefix of the name of the position category used to keep track of the master
document's fragments that correspond to the segments of the projection
document.
/**
* Prefix of the name of the position category used to keep track of the master
* document's fragments that correspond to the segments of the projection
* document.
*/
private final static String FRAGMENTS_CATEGORY_PREFIX= "__fragmentsCategory"; //$NON-NLS-1$
Name of the position category used to keep track of the project
document's segments that correspond to the fragments of the master
document.
/**
* Name of the position category used to keep track of the project
* document's segments that correspond to the fragments of the master
* document.
*/
private final static String SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$
The master document /** The master document */
private IDocument fMasterDocument;
The master document as document extension /** The master document as document extension */
private IDocumentExtension fMasterDocumentExtension;
The fragments' position category /** The fragments' position category */
private String fFragmentsCategory;
The segment's position category /** The segment's position category */
private String fSegmentsCategory;
The document event issued by the master document /** The document event issued by the master document */
private DocumentEvent fMasterEvent;
The document event to be issued by the projection document /** The document event to be issued by the projection document */
private ProjectionDocumentEvent fSlaveEvent;
The original document event generated by a direct manipulation of this projection document /** The original document event generated by a direct manipulation of this projection document */
private DocumentEvent fOriginalEvent;
Indicates whether the projection document initiated a master document update or not /** Indicates whether the projection document initiated a master document update or not */
private boolean fIsUpdating= false;
Indicated whether the projection document is in auto expand mode nor not /** Indicated whether the projection document is in auto expand mode nor not */
private boolean fIsAutoExpanding= false;
The position updater for the segments /** The position updater for the segments */
private SegmentUpdater fSegmentUpdater;
The position updater for the fragments /** The position updater for the fragments */
private FragmentUpdater fFragmentsUpdater;
The projection mapping /** The projection mapping */
private ProjectionMapping fMapping;
Creates a projection document for the given master document.
Params: - masterDocument – the master document
/**
* Creates a projection document for the given master document.
*
* @param masterDocument the master document
*/
public ProjectionDocument(IDocument masterDocument) {
super();
fMasterDocument= masterDocument;
if (fMasterDocument instanceof IDocumentExtension)
fMasterDocumentExtension= (IDocumentExtension) fMasterDocument;
fSegmentsCategory= SEGMENTS_CATEGORY;
fFragmentsCategory= FRAGMENTS_CATEGORY_PREFIX + hashCode();
fMasterDocument.addPositionCategory(fFragmentsCategory);
fFragmentsUpdater= new FragmentUpdater(fFragmentsCategory);
fMasterDocument.addPositionUpdater(fFragmentsUpdater);
fMapping= new ProjectionMapping(masterDocument, fFragmentsCategory, this, fSegmentsCategory);
ITextStore s= new ProjectionTextStore(masterDocument, fMapping);
ILineTracker tracker= new DefaultLineTracker();
setTextStore(s);
setLineTracker(tracker);
completeInitialization();
initializeProjection();
tracker.set(s.get(0, s.getLength()));
}
Disposes this projection document.
/**
* Disposes this projection document.
*/
public void dispose() {
fMasterDocument.removePositionUpdater(fFragmentsUpdater);
try {
fMasterDocument.removePositionCategory(fFragmentsCategory);
} catch (BadPositionCategoryException x) {
// allow multiple dispose calls
}
}
private void internalError() {
throw new IllegalStateException();
}
Returns the fragments of the master documents.
Returns: the fragment of the master document
/**
* Returns the fragments of the master documents.
*
* @return the fragment of the master document
*/
protected final Position[] getFragments() {
try {
return fMasterDocument.getPositions(fFragmentsCategory);
} catch (BadPositionCategoryException e) {
internalError();
}
// unreachable
return null;
}
Returns the segments of this projection document.
Returns: the segments of this projection document
/**
* Returns the segments of this projection document.
*
* @return the segments of this projection document
*/
protected final Position[] getSegments() {
try {
return getPositions(fSegmentsCategory);
} catch (BadPositionCategoryException e) {
internalError();
}
// unreachable
return null;
}
Returns the projection mapping used by this document.
Returns: the projection mapping used by this document Deprecated: As of 3.4, replaced by getDocumentInformationMapping()
/**
* Returns the projection mapping used by this document.
*
* @return the projection mapping used by this document
* @deprecated As of 3.4, replaced by {@link #getDocumentInformationMapping()}
*/
@Deprecated
public ProjectionMapping getProjectionMapping(){
return fMapping;
}
Returns the projection mapping used by this document.
Returns: the projection mapping used by this document Since: 3.4
/**
* Returns the projection mapping used by this document.
*
* @return the projection mapping used by this document
* @since 3.4
*/
public IDocumentInformationMapping getDocumentInformationMapping() {
return fMapping;
}
Returns the master document of this projection document.
Returns: the master document of this projection document
/**
* Returns the master document of this projection document.
*
* @return the master document of this projection document
*/
public IDocument getMasterDocument() {
return fMasterDocument;
}
@Override
public String getDefaultLineDelimiter() {
return TextUtilities.getDefaultLineDelimiter(fMasterDocument);
}
Initializes the projection document from the master document based on
the master's fragments.
/**
* Initializes the projection document from the master document based on
* the master's fragments.
*/
private void initializeProjection() {
try {
addPositionCategory(fSegmentsCategory);
fSegmentUpdater= new SegmentUpdater(fSegmentsCategory);
addPositionUpdater(fSegmentUpdater);
int offset= 0;
Position[] fragments= getFragments();
for (Position f : fragments) {
Fragment fragment = (Fragment) f;
Segment segment= new Segment(offset, fragment.getLength());
segment.fragment= fragment;
addPosition(fSegmentsCategory, segment);
offset += fragment.length;
}
} catch (BadPositionCategoryException x) {
internalError();
} catch (BadLocationException x) {
internalError();
}
}
Creates a segment for the given fragment at the given position inside the list of segments.
Params: - fragment – the corresponding fragment
- index – the index in the list of segments
Throws: - BadLocationException – in case the fragment is invalid
- BadPositionCategoryException – in case the segment category is invalid
Returns: the created segment
/**
* Creates a segment for the given fragment at the given position inside the list of segments.
*
* @param fragment the corresponding fragment
* @param index the index in the list of segments
* @return the created segment
* @throws BadLocationException in case the fragment is invalid
* @throws BadPositionCategoryException in case the segment category is invalid
*/
private Segment createSegmentFor(Fragment fragment, int index) throws BadLocationException, BadPositionCategoryException {
int offset= 0;
if (index > 0) {
Position[] segments= getSegments();
Segment segment= (Segment) segments[index - 1];
offset= segment.getOffset() + segment.getLength();
}
Segment segment= new Segment(offset, 0);
segment.fragment= fragment;
fragment.segment= segment;
addPosition(fSegmentsCategory, segment);
return segment;
}
Adds the given range of the master document to this projection document.
Params: - offsetInMaster – offset of the master document range
- lengthInMaster – length of the master document range
- masterDocumentEvent – the master document event that causes this
projection change or
null
if none
Throws: - BadLocationException – if the given range is invalid in the master
document
/**
* Adds the given range of the master document to this projection document.
*
* @param offsetInMaster offset of the master document range
* @param lengthInMaster length of the master document range
* @param masterDocumentEvent the master document event that causes this
* projection change or <code>null</code> if none
* @throws BadLocationException if the given range is invalid in the master
* document
*/
private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
if (lengthInMaster == 0)
return;
try {
Position[] fragments= getFragments();
int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, offsetInMaster);
Fragment left= null;
Fragment right= null;
if (index < fragments.length) {
Fragment fragment= (Fragment) fragments[index];
if (offsetInMaster == fragment.offset)
if (fragment.length == 0) // the fragment does not overlap - it is a zero-length fragment at the same offset
left= fragment;
else
throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$
if (offsetInMaster + lengthInMaster == fragment.offset)
right= fragment;
}
if (0 < index && index <= fragments.length) {
Fragment fragment= (Fragment) fragments[index - 1];
if (fragment.includes(offsetInMaster))
throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$
if (fragment.getOffset() + fragment.getLength() == offsetInMaster)
left= fragment;
}
int offsetInSlave= 0;
if (index > 0) {
Fragment fragment= (Fragment) fragments[index - 1];
Segment segment= fragment.segment;
offsetInSlave= segment.getOffset() + segment.getLength();
}
ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, offsetInSlave, 0, fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster, masterDocumentEvent);
super.fireDocumentAboutToBeChanged(event);
// check for neighboring fragment
if (left != null && right != null) {
int endOffset= right.getOffset() + right.getLength();
left.setLength(endOffset - left.getOffset());
left.segment.setLength(left.segment.getLength() + right.segment.getLength());
removePosition(fSegmentsCategory, right.segment);
fMasterDocument.removePosition(fFragmentsCategory, right);
} else if (left != null) {
int endOffset= offsetInMaster +lengthInMaster;
left.setLength(endOffset - left.getOffset());
left.segment.markForStretch();
} else if (right != null) {
right.setOffset(right.getOffset() - lengthInMaster);
right.setLength(right.getLength() + lengthInMaster);
right.segment.markForStretch();
} else {
// create a new segment
Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
fMasterDocument.addPosition(fFragmentsCategory, fragment);
Segment segment= createSegmentFor(fragment, index);
segment.markForStretch();
}
getTracker().replace(event.getOffset(), event.getLength(), event.getText());
super.fireDocumentChanged(event);
} catch (BadPositionCategoryException x) {
internalError();
}
}
Finds the fragment of the master document that represents the given range.
Params: - offsetInMaster – the offset of the range in the master document
- lengthInMaster – the length of the range in the master document
Returns: the fragment representing the given master document range
/**
* Finds the fragment of the master document that represents the given range.
*
* @param offsetInMaster the offset of the range in the master document
* @param lengthInMaster the length of the range in the master document
* @return the fragment representing the given master document range
*/
private Fragment findFragment(int offsetInMaster, int lengthInMaster) {
Position[] fragments= getFragments();
for (Position fragment : fragments) {
Fragment f = (Fragment) fragment;
if (f.getOffset() <= offsetInMaster && offsetInMaster + lengthInMaster <= f.getOffset() + f.getLength())
return f;
}
return null;
}
Removes the given range of the master document from this projection
document.
Params: - offsetInMaster – the offset of the range in the master document
- lengthInMaster – the length of the range in the master document
Throws: - BadLocationException – if the given range is not valid in the
master document
- IllegalArgumentException – if the given range is not projected in
this projection document or is not completely comprised by
an existing fragment
/**
* Removes the given range of the master document from this projection
* document.
*
* @param offsetInMaster the offset of the range in the master document
* @param lengthInMaster the length of the range in the master document
*
* @throws BadLocationException if the given range is not valid in the
* master document
* @throws IllegalArgumentException if the given range is not projected in
* this projection document or is not completely comprised by
* an existing fragment
*/
private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
try {
IRegion imageRegion= fMapping.toExactImageRegion(new Region(offsetInMaster, lengthInMaster));
if (imageRegion == null)
throw new IllegalArgumentException();
Fragment fragment= findFragment(offsetInMaster, lengthInMaster);
if (fragment == null)
throw new IllegalArgumentException();
ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), "", offsetInMaster, lengthInMaster); //$NON-NLS-1$
super.fireDocumentAboutToBeChanged(event);
if (fragment.getOffset() == offsetInMaster) {
fragment.setOffset(offsetInMaster + lengthInMaster);
fragment.setLength(fragment.getLength() - lengthInMaster);
} else {
// split fragment into three fragments, let position updater remove it
// add fragment for the region to be removed
Fragment newFragment= new Fragment(offsetInMaster, lengthInMaster);
Segment segment= new Segment(imageRegion.getOffset(), imageRegion.getLength());
newFragment.segment= segment;
segment.fragment= newFragment;
fMasterDocument.addPosition(fFragmentsCategory, newFragment);
addPosition(fSegmentsCategory, segment);
// add fragment for the remainder right of the deleted range in the original fragment
int offset= offsetInMaster + lengthInMaster;
newFragment= new Fragment(offset, fragment.getOffset() + fragment.getLength() - offset);
offset= imageRegion.getOffset() + imageRegion.getLength();
segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset);
newFragment.segment= segment;
segment.fragment= newFragment;
fMasterDocument.addPosition(fFragmentsCategory, newFragment);
addPosition(fSegmentsCategory, segment);
// adjust length of initial fragment (the left one)
fragment.setLength(offsetInMaster - fragment.getOffset());
fragment.segment.setLength(imageRegion.getOffset() - fragment.segment.getOffset());
}
getTracker().replace(event.getOffset(), event.getLength(), event.getText());
super.fireDocumentChanged(event);
} catch (BadPositionCategoryException x) {
internalError();
}
}
Returns the sequence of all master document regions which are contained
in the given master document range and which are not yet part of this
projection document.
Params: - offsetInMaster – the range offset in the master document
- lengthInMaster – the range length in the master document
Throws: - BadLocationException – in case the given range is invalid in the
master document
Returns: the sequence of regions which are not yet part of the projection
document
/**
* Returns the sequence of all master document regions which are contained
* in the given master document range and which are not yet part of this
* projection document.
*
* @param offsetInMaster the range offset in the master document
* @param lengthInMaster the range length in the master document
* @return the sequence of regions which are not yet part of the projection
* document
* @throws BadLocationException in case the given range is invalid in the
* master document
*/
public final IRegion[] computeUnprojectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
IRegion[] fragments= null;
IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
if (imageRegion != null)
fragments= fMapping.toExactOriginRegions(imageRegion);
if (fragments == null || fragments.length == 0)
return new IRegion[] { new Region(offsetInMaster, lengthInMaster) };
List<Region> gaps= new ArrayList<>();
IRegion region= fragments[0];
if (offsetInMaster < region.getOffset())
gaps.add(new Region(offsetInMaster, region.getOffset() - offsetInMaster));
for (int i= 0; i < fragments.length - 1; i++) {
IRegion left= fragments[i];
IRegion right= fragments[i + 1];
int leftEnd= left.getOffset() + left.getLength();
if (leftEnd < right.getOffset())
gaps.add(new Region(leftEnd, right.getOffset() - leftEnd));
}
region= fragments[fragments.length - 1];
int leftEnd= region.getOffset() + region.getLength();
int rightEnd= offsetInMaster + lengthInMaster;
if (leftEnd < rightEnd)
gaps.add(new Region(leftEnd, rightEnd - leftEnd));
IRegion[] result= new IRegion[gaps.size()];
gaps.toArray(result);
return result;
}
Returns the first master document region which is contained in the given
master document range and which is not yet part of this projection
document.
Params: - offsetInMaster – the range offset in the master document
- lengthInMaster – the range length in the master document
Throws: - BadLocationException – in case the given range is invalid in the
master document
Returns: the first region that is not yet part of the projection document Since: 3.1
/**
* Returns the first master document region which is contained in the given
* master document range and which is not yet part of this projection
* document.
*
* @param offsetInMaster the range offset in the master document
* @param lengthInMaster the range length in the master document
* @return the first region that is not yet part of the projection document
* @throws BadLocationException in case the given range is invalid in the
* master document
* @since 3.1
*/
private IRegion computeFirstUnprojectedMasterRegion(int offsetInMaster, int lengthInMaster) throws BadLocationException {
IRegion[] fragments= null;
IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
if (imageRegion != null)
fragments= fMapping.toExactOriginRegions(imageRegion);
if (fragments == null || fragments.length == 0)
return new Region(offsetInMaster, lengthInMaster);
IRegion region= fragments[0];
if (offsetInMaster < region.getOffset())
return new Region(offsetInMaster, region.getOffset() - offsetInMaster);
for (int i= 0; i < fragments.length - 1; i++) {
IRegion left= fragments[i];
IRegion right= fragments[i + 1];
int leftEnd= left.getOffset() + left.getLength();
if (leftEnd < right.getOffset())
return new Region(leftEnd, right.getOffset() - leftEnd);
}
region= fragments[fragments.length - 1];
int leftEnd= region.getOffset() + region.getLength();
int rightEnd= offsetInMaster + lengthInMaster;
if (leftEnd < rightEnd)
return new Region(leftEnd, rightEnd - leftEnd);
return null;
}
Ensures that the given range of the master document is part of this
projection document.
Params: - offsetInMaster – the offset of the master document range
- lengthInMaster – the length of the master document range
Throws: - BadLocationException – in case the master event is not valid
/**
* Ensures that the given range of the master document is part of this
* projection document.
*
* @param offsetInMaster the offset of the master document range
* @param lengthInMaster the length of the master document range
* @throws BadLocationException in case the master event is not valid
*/
public void addMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
addMasterDocumentRange(offsetInMaster, lengthInMaster, null);
}
Ensures that the given range of the master document is part of this
projection document.
Params: - offsetInMaster – the offset of the master document range
- lengthInMaster – the length of the master document range
- masterDocumentEvent – the master document event which causes this
projection change, or
null
if none
Throws: - BadLocationException – in case the master event is not valid
/**
* Ensures that the given range of the master document is part of this
* projection document.
*
* @param offsetInMaster the offset of the master document range
* @param lengthInMaster the length of the master document range
* @param masterDocumentEvent the master document event which causes this
* projection change, or <code>null</code> if none
* @throws BadLocationException in case the master event is not valid
*/
private void addMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
/*
* Calling internalAddMasterDocumentRange may cause other master ranges
* to become unfolded, resulting in re-entrant calls to this method. In
* order to not add a region twice, we have to compute the next region
* to add in every iteration.
*
* To place an upper bound on the number of iterations, we use the number
* of fragments * 2 as the limit.
*/
int limit= Math.max(getFragments().length * 2, 20);
while (true) {
if (limit-- < 0)
throw new IllegalArgumentException("safety loop termination"); //$NON-NLS-1$
IRegion gap= computeFirstUnprojectedMasterRegion(offsetInMaster, lengthInMaster);
if (gap == null)
return;
internalAddMasterDocumentRange(gap.getOffset(), gap.getLength(), masterDocumentEvent);
}
}
Ensures that the given range of the master document is not part of this
projection document.
Params: - offsetInMaster – the offset of the master document range
- lengthInMaster – the length of the master document range
Throws: - BadLocationException – in case the master event is not valid
/**
* Ensures that the given range of the master document is not part of this
* projection document.
*
* @param offsetInMaster the offset of the master document range
* @param lengthInMaster the length of the master document range
* @throws BadLocationException in case the master event is not valid
*/
public void removeMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
IRegion[] fragments= computeProjectedMasterRegions(offsetInMaster, lengthInMaster);
if (fragments == null || fragments.length == 0)
return;
for (IRegion fragment : fragments) {
internalRemoveMasterDocumentRange(fragment.getOffset(), fragment.getLength());
}
}
Returns the sequence of all master document regions with are contained in the given master document
range and which are part of this projection document. May return null
if no such
regions exist.
Params: - offsetInMaster – the range offset in the master document
- lengthInMaster – the range length in the master document
Throws: - BadLocationException – in case the given range is invalid in the master document
Returns: the sequence of regions which are part of the projection document or null
/**
* Returns the sequence of all master document regions with are contained in the given master document
* range and which are part of this projection document. May return <code>null</code> if no such
* regions exist.
*
* @param offsetInMaster the range offset in the master document
* @param lengthInMaster the range length in the master document
* @return the sequence of regions which are part of the projection document or <code>null</code>
* @throws BadLocationException in case the given range is invalid in the master document
*/
public final IRegion[] computeProjectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
return imageRegion != null ? fMapping.toExactOriginRegions(imageRegion) : null;
}
Returns whether this projection is being updated.
Returns: true
if the document is updating
/**
* Returns whether this projection is being updated.
*
* @return <code>true</code> if the document is updating
*/
protected boolean isUpdating() {
return fIsUpdating;
}
@Override
public void replace(int offset, int length, String text) throws BadLocationException {
try {
fIsUpdating= true;
if (fMasterDocumentExtension != null)
fMasterDocumentExtension.stopPostNotificationProcessing();
super.replace(offset, length, text);
} finally {
fIsUpdating= false;
if (fMasterDocumentExtension != null)
fMasterDocumentExtension.resumePostNotificationProcessing();
}
}
@Override
public void set(String text) {
try {
fIsUpdating= true;
if (fMasterDocumentExtension != null)
fMasterDocumentExtension.stopPostNotificationProcessing();
super.set(text);
} finally {
fIsUpdating= false;
if (fMasterDocumentExtension != null)
fMasterDocumentExtension.resumePostNotificationProcessing();
}
}
Transforms a document event of the master document into a projection
document based document event.
Params: - masterEvent – the master document event
Throws: - BadLocationException – in case the master event is not valid
Returns: the slave document event
/**
* Transforms a document event of the master document into a projection
* document based document event.
*
* @param masterEvent the master document event
* @return the slave document event
* @throws BadLocationException in case the master event is not valid
*/
private ProjectionDocumentEvent normalize(DocumentEvent masterEvent) throws BadLocationException {
if (!isUpdating()) {
IRegion imageRegion= fMapping.toExactImageRegion(new Region(masterEvent.getOffset(), masterEvent.getLength()));
if (imageRegion != null)
return new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), masterEvent.getText(), masterEvent);
return null;
}
ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, fOriginalEvent.getOffset(), fOriginalEvent.getLength(), fOriginalEvent.getText(), masterEvent);
fOriginalEvent= null;
return event;
}
Ensures that when the master event affects this projection document, that the whole region described by the
event is part of this projection document.
Params: - masterEvent – the master document event
Throws: - BadLocationException – in case the master event is not valid
Returns: true
if masterEvent affects this projection document
/**
* Ensures that when the master event affects this projection document, that the whole region described by the
* event is part of this projection document.
*
* @param masterEvent the master document event
* @return <code>true</code> if masterEvent affects this projection document
* @throws BadLocationException in case the master event is not valid
*/
protected final boolean adaptProjectionToMasterChange(DocumentEvent masterEvent) throws BadLocationException {
if (!isUpdating() && fFragmentsUpdater.affectsPositions(masterEvent) || fIsAutoExpanding && masterEvent.getLength() > 0) {
addMasterDocumentRange(masterEvent.getOffset(), masterEvent.getLength(), masterEvent);
return true;
} else if (fMapping.getImageLength() == 0 && masterEvent.getLength() == 0) {
Position[] fragments= getFragments();
if (fragments.length == 0) {
// there is no segment in this projection document, thus one must be created
// need to bypass the usual infrastructure as the new segment/fragment would be of length 0 and thus the segmentation be not well formed
try {
Fragment fragment= new Fragment(0, 0);
fMasterDocument.addPosition(fFragmentsCategory, fragment);
createSegmentFor(fragment, 0);
} catch (BadPositionCategoryException x) {
internalError();
}
}
}
return isUpdating();
}
When called, this projection document is informed about a forthcoming
change of its master document. This projection document checks whether
the master document change affects it and if so informs all document
listeners.
Params: - masterEvent – the master document event
/**
* When called, this projection document is informed about a forthcoming
* change of its master document. This projection document checks whether
* the master document change affects it and if so informs all document
* listeners.
*
* @param masterEvent the master document event
*/
public void masterDocumentAboutToBeChanged(DocumentEvent masterEvent) {
try {
boolean assertNotNull= adaptProjectionToMasterChange(masterEvent);
fSlaveEvent= normalize(masterEvent);
if (assertNotNull && fSlaveEvent == null)
internalError();
fMasterEvent= masterEvent;
if (fSlaveEvent != null)
delayedFireDocumentAboutToBeChanged();
} catch (BadLocationException e) {
internalError();
}
}
When called, this projection document is informed about a change of its
master document. If this projection document is affected it informs all
of its document listeners.
Params: - masterEvent – the master document event
/**
* When called, this projection document is informed about a change of its
* master document. If this projection document is affected it informs all
* of its document listeners.
*
* @param masterEvent the master document event
*/
public void masterDocumentChanged(DocumentEvent masterEvent) {
if ( !isUpdating() && masterEvent == fMasterEvent) {
if (fSlaveEvent != null) {
try {
getTracker().replace(fSlaveEvent.getOffset(), fSlaveEvent.getLength(), fSlaveEvent.getText());
fireDocumentChanged(fSlaveEvent);
} catch (BadLocationException e) {
internalError();
}
} else if (ensureWellFormedSegmentation(masterEvent.getOffset()))
fMapping.projectionChanged();
}
}
@Override
protected void fireDocumentAboutToBeChanged(DocumentEvent event) {
fOriginalEvent= event;
// delay it until there is a notification from the master document
// at this point, it is expensive to construct the master document information
}
Fires the slave document event as about-to-be-changed event to all registered listeners.
/**
* Fires the slave document event as about-to-be-changed event to all registered listeners.
*/
private void delayedFireDocumentAboutToBeChanged() {
super.fireDocumentAboutToBeChanged(fSlaveEvent);
}
Ignores the given event and sends the semantically equal slave document event instead.
Params: - event – the event to be ignored
/**
* Ignores the given event and sends the semantically equal slave document event instead.
*
* @param event the event to be ignored
*/
@Override
protected void fireDocumentChanged(DocumentEvent event) {
super.fireDocumentChanged(fSlaveEvent);
}
@Override
protected void updateDocumentStructures(DocumentEvent event) {
super.updateDocumentStructures(event);
ensureWellFormedSegmentation(computeAnchor(event));
fMapping.projectionChanged();
}
private int computeAnchor(DocumentEvent event) {
if (event instanceof ProjectionDocumentEvent) {
ProjectionDocumentEvent slave= (ProjectionDocumentEvent) event;
Object changeType= slave.getChangeType();
if (ProjectionDocumentEvent.CONTENT_CHANGE == changeType) {
DocumentEvent master= slave.getMasterEvent();
if (master != null)
return master.getOffset();
} else if (ProjectionDocumentEvent.PROJECTION_CHANGE == changeType) {
return slave.getMasterOffset();
}
}
return -1;
}
private boolean ensureWellFormedSegmentation(int anchorOffset) {
boolean changed= false;
Position[] segments= getSegments();
for (int i= 0; i < segments.length; i++) {
Segment segment= (Segment) segments[i];
if (segment.isDeleted() || (segment.getLength() == 0 && (i < segments.length - 1 || (i > 0 && segments[i - 1].isDeleted())))) {
try {
removePosition(fSegmentsCategory, segment);
fMasterDocument.removePosition(fFragmentsCategory, segment.fragment);
changed= true;
} catch (BadPositionCategoryException e) {
internalError();
}
} else if (i < segments.length - 1) {
Segment next= (Segment) segments[i + 1];
if (next.isDeleted() || next.getLength() == 0)
continue;
Fragment fragment= segment.fragment;
if (fragment.getOffset() + fragment.getLength() == next.fragment.getOffset()) {
// join fragments and their corresponding segments
segment.setLength(segment.getLength() + next.getLength());
fragment.setLength(fragment.getLength() + next.fragment.getLength());
next.delete();
}
}
}
if (changed && anchorOffset != -1) {
Position[] changedSegments= getSegments();
if (changedSegments == null || changedSegments.length == 0) {
Fragment fragment= new Fragment(anchorOffset, 0);
try {
fMasterDocument.addPosition(fFragmentsCategory, fragment);
createSegmentFor(fragment, 0);
} catch (BadLocationException e) {
internalError();
} catch (BadPositionCategoryException e) {
internalError();
}
}
}
return changed;
}
@Override
public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
if (!isUpdating())
throw new UnsupportedOperationException();
super.registerPostNotificationReplace(owner, replace);
}
Sets the auto expand mode for this document.
Params: - autoExpandMode –
true
if auto-expanding
/**
* Sets the auto expand mode for this document.
*
* @param autoExpandMode <code>true</code> if auto-expanding
*/
public void setAutoExpandMode(boolean autoExpandMode) {
fIsAutoExpanding= autoExpandMode;
}
Replaces all master document ranges with the given master document range.
Params: - offsetInMaster – the offset in the master document
- lengthInMaster – the length in the master document
Throws: - BadLocationException – if the given range of the master document is not valid
/**
* Replaces all master document ranges with the given master document range.
*
* @param offsetInMaster the offset in the master document
* @param lengthInMaster the length in the master document
* @throws BadLocationException if the given range of the master document is not valid
*/
public void replaceMasterDocumentRanges(int offsetInMaster, int lengthInMaster) throws BadLocationException {
try {
ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, 0, fMapping.getImageLength(), fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster);
super.fireDocumentAboutToBeChanged(event);
Position[] fragments= getFragments();
for (Position fragment1 : fragments) {
Fragment fragment = (Fragment) fragment1;
fMasterDocument.removePosition(fFragmentsCategory, fragment);
removePosition(fSegmentsCategory, fragment.segment);
}
Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
Segment segment= new Segment(0, 0);
segment.fragment= fragment;
fragment.segment= segment;
fMasterDocument.addPosition(fFragmentsCategory, fragment);
addPosition(fSegmentsCategory, segment);
getTracker().set(fMasterDocument.get(offsetInMaster, lengthInMaster));
super.fireDocumentChanged(event);
} catch (BadPositionCategoryException x) {
internalError();
}
}
}