/******************************************************************************* * Copyright (c) 2000, 2010 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 * 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 <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. */ 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. */ private final static String SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$ /** The master document */ private IDocument fMasterDocument; /** The master document as document extension */ private IDocumentExtension fMasterDocumentExtension; /** The fragments' position category */ private String fFragmentsCategory; /** The segment's position category */ private String fSegmentsCategory; /** The document event issued by the master document */ private DocumentEvent fMasterEvent; /** 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 */ private DocumentEvent fOriginalEvent; /** 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 */ private boolean fIsAutoExpanding= false; /** The position updater for the segments */ private SegmentUpdater fSegmentUpdater; /** The position updater for the fragments */ private FragmentUpdater fFragmentsUpdater; /** The projection mapping */ private ProjectionMapping fMapping; /** * 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. */ 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. * * @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. * * @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. * * @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. * * @return the projection mapping used by this document * @since 3.4 */ public IDocumentInformationMapping getDocumentInformationMapping() { return fMapping; } /** * 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. */ private void initializeProjection() { try { addPositionCategory(fSegmentsCategory); fSegmentUpdater= new SegmentUpdater(fSegmentsCategory); addPositionUpdater(fSegmentUpdater); int offset= 0; Position[] fragments= getFragments(); for (int i= 0; i < fragments.length; i++) { Fragment fragment= (Fragment) fragments[i]; 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. * * @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. * * @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. * * @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 (int i= 0; i < fragments.length; i++) { Fragment f= (Fragment) fragments[i]; 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. * * @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. * * @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. * * @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. * * @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. * * @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. * * @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 (int i= 0; i < fragments.length; i++) { IRegion fragment= fragments[i]; 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 <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. * * @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. * * @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. * * @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. * * @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. * * @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. */ private void delayedFireDocumentAboutToBeChanged() { super.fireDocumentAboutToBeChanged(fSlaveEvent); } /** * 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. * * @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. * * @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 (int i= 0; i < fragments.length; i++) { Fragment fragment= (Fragment) fragments[i]; 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(); } } }