diff options
author | Kai Maetzel | 2004-03-12 18:19:37 +0000 |
---|---|---|
committer | Kai Maetzel | 2004-03-12 18:19:37 +0000 |
commit | bdad092ab72bb499f55b6d6dd4577a2319295cc7 (patch) | |
tree | a95b607dba8f4a8043b3effc0cb36e2995e51c24 /org.eclipse.text/projection/org | |
parent | da89adec08f45013179dab1423381b51de4e9b34 (diff) | |
download | eclipse.platform.text-bdad092ab72bb499f55b6d6dd4577a2319295cc7.tar.gz eclipse.platform.text-bdad092ab72bb499f55b6d6dd4577a2319295cc7.tar.xz eclipse.platform.text-bdad092ab72bb499f55b6d6dd4577a2319295cc7.zip |
Rework of projection documents
Diffstat (limited to 'org.eclipse.text/projection/org')
18 files changed, 1894 insertions, 1 deletions
diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/CoordinatesTranslator.java b/org.eclipse.text/projection/org/eclipse/jface/text/CoordinatesTranslator.java index b2b5547a015..eb4af1d45da 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/CoordinatesTranslator.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/CoordinatesTranslator.java @@ -20,6 +20,7 @@ import java.util.Comparator; * document is considered the image document.<p> * This class is for internal use only. * @since 2.1 + * @deprecated */ public class CoordinatesTranslator implements IDocumentInformationMapping { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/Fragment.java b/org.eclipse.text/projection/org/eclipse/jface/text/Fragment.java index f19078e2c00..ffaf01716d9 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/Fragment.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/Fragment.java @@ -16,6 +16,7 @@ package org.eclipse.jface.text; * This class is for internal use only. * * @since 2.1 + * @deprecated */ public class Fragment extends Position { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/FragmentUpdater.java b/org.eclipse.text/projection/org/eclipse/jface/text/FragmentUpdater.java index e24c8efffc2..c8c0732d632 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/FragmentUpdater.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/FragmentUpdater.java @@ -22,6 +22,7 @@ package org.eclipse.jface.text; * This class is for internal use only. * * @since 2.1 + * @deprecated */ public class FragmentUpdater extends DefaultPositionUpdater { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocument.java b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocument.java index 5cd17f7e3d7..9cbf9dbcfcb 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocument.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocument.java @@ -25,6 +25,7 @@ import java.util.Comparator; * This class if for internal use only. * * @since 2.1 + * @deprecated */ public final class ProjectionDocument extends AbstractDocument { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocumentManager.java b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocumentManager.java index 51d825d6359..63c69bceafa 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocumentManager.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionDocumentManager.java @@ -34,6 +34,7 @@ import java.util.Map; * This class if for internal use only. * * @since 2.1 + * @deprecated */ public final class ProjectionDocumentManager implements IDocumentListener, ISlaveDocumentManager { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPosition.java b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPosition.java index 30fca736574..1724a1332d0 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPosition.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPosition.java @@ -12,9 +12,10 @@ package org.eclipse.jface.text; /** * Represents the corresponding parent document range of a fragment of a <code>ProjectionDocument</code>.<p> - * This calss is for internal use only. + * This class is for internal use only. * * @since 2.1 + * @deprecated */ public class ProjectionPosition extends Position { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPositionUpdater.java b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPositionUpdater.java index 166193caa14..1ad05dd4560 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPositionUpdater.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionPositionUpdater.java @@ -22,6 +22,7 @@ package org.eclipse.jface.text; * This class is for internal use only. * * @since 2.1 + * @deprecated */ public class ProjectionPositionUpdater extends DefaultPositionUpdater { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionTextStore.java b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionTextStore.java index 288010c4aff..b163d040dda 100644 --- a/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionTextStore.java +++ b/org.eclipse.text/projection/org/eclipse/jface/text/ProjectionTextStore.java @@ -18,6 +18,7 @@ package org.eclipse.jface.text; * This class is for internal use only. * * @since 2.1 + * @deprecated */ public class ProjectionTextStore implements ITextStore { diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/Fragment.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/Fragment.java new file mode 100644 index 00000000000..67449998115 --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/Fragment.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.Position; + +/** + * Internal class. Do not use. Only public for testing purposes. + * + * @since 3.0 + */ +public class Fragment extends Position { + + public Segment segment; + + public Fragment(int offset, int length) { + super(offset, length); + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/FragmentUpdater.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/FragmentUpdater.java new file mode 100644 index 00000000000..7a503749ec3 --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/FragmentUpdater.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DefaultPositionUpdater; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.Position; + + +/** + * The position updater used to adapt the fragments of a document. If an + * insertion happens at a fragment's offset, the fragment is extended rather + * than shifted. Also, the last fragment is extended if an insert operation + * happens at the end of the fragment. + * <p> + * Internal class. Do not use. Only public for testing purposes. + * + * @since 3.0 + */ +public class FragmentUpdater extends DefaultPositionUpdater { + + /** Indicates whether the position being updated represents the last fragment. */ + private boolean fIsLast= false; + + /** + * Creates the fragment updater for the given category. + * + * @param fragmentCategory the position category used for managing the fragments of a document + */ + protected FragmentUpdater(String fragmentCategory) { + super(fragmentCategory); + } + + /* + * @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent) + */ + public void update(DocumentEvent event) { + + try { + + Position[] category= event.getDocument().getPositions(getCategory()); + + fOffset= event.getOffset(); + fLength= event.getLength(); + fReplaceLength= (event.getText() == null ? 0 : event.getText().length()); + fDocument= event.getDocument(); + + for (int i= 0; i < category.length; i++) { + + fPosition= category[i]; + fIsLast= (i == category.length -1); + + fOriginalPosition.offset= fPosition.offset; + fOriginalPosition.length= fPosition.length; + + if (notDeleted()) + adaptToReplace(); + } + + } catch (BadPositionCategoryException x) { + // do nothing + } + } + + /* + * @see org.eclipse.jface.text.DefaultPositionUpdater#adaptToInsert() + */ + protected void adaptToInsert() { + int myStart= fPosition.offset; + int myEnd= Math.max(myStart, fPosition.offset + fPosition.length - (fIsLast || isAffectingReplace() ? 0 : 1)); + + if (myEnd < fOffset) + return; + + if (fLength <= 0) { + + if (myStart <= fOffset) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + + } else { + + if (myStart <= fOffset && fOriginalPosition.offset <= fOffset) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + } + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/IMinimalMapping.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/IMinimalMapping.java new file mode 100644 index 00000000000..19cf110dcee --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/IMinimalMapping.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IRegion; + +/** + * Internal class. Do not use. + * + * @since 3.0 + */ +interface IMinimalMapping { + + /** + * @return + */ + IRegion getCoverage(); + + /** + * @param region + * @return + */ + IRegion toOriginRegion(IRegion region) throws BadLocationException; + + /** + * @param offset + * @return + */ + int toOriginOffset(int offset) throws BadLocationException; + + /** + * @return + */ + IRegion[] toExactOriginRegions(IRegion region)throws BadLocationException; + + /** + * @return + */ + int getImageLength(); +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java new file mode 100644 index 00000000000..1bafeffe841 --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java @@ -0,0 +1,613 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +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.Assert; +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.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; + + + +/** + * 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 utilizes its master document as + * <code>ITextStore</code>. + * <p> + * API in progress. Do not yet use. + * + * @since 3.0 + */ +public class ProjectionDocument extends AbstractDocument { + + /** 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 issued and to be issued by the projection document */ + private ProjectionDocumentEvent fSlaveEvent; + /** 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 projection mapping */ + private ProjectionMapping fMapping; + + /** + * Creates a projection document for the given master document. + * + * @param masterDocument the master document + * @param fragmentsCategory the document position category managing the master's fragments + * @param segmentsCategory the document position category managing the segments + */ + public ProjectionDocument(IDocument masterDocument, String fragmentsCategory, String segmentsCategory) { + super(); + + fMasterDocument= masterDocument; + if (fMasterDocument instanceof IDocumentExtension) + fMasterDocumentExtension= (IDocumentExtension) fMasterDocument; + + fFragmentsCategory= fragmentsCategory; + fSegmentsCategory= segmentsCategory; + fMapping= new ProjectionMapping(masterDocument, fragmentsCategory, this, segmentsCategory); + + ITextStore s= new ProjectionTextStore(masterDocument, fMapping); + ILineTracker tracker= new DefaultLineTracker(); + + setTextStore(s); + setLineTracker(tracker); + + completeInitialization(); + + initializeProjection(); + tracker.set(s.get(0, s.getLength())); + } + + private void internalError() { + throw new IllegalStateException(); + } + + protected final Position[] getFragments() { + try { + return fMasterDocument.getPositions(fFragmentsCategory); + } catch (BadPositionCategoryException e) { + internalError(); + } + // unreachable + return null; + } + + 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 + */ + public ProjectionMapping getProjectionMapping(){ + return fMapping; + } + + /** + * Returns the master document of this projection document. + * + * @return the master document of this projection document + */ + public IDocument getMasterDocument() { + return 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(); + } + } + + 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 + * @throws BadLocationException if the given range is invalid in the master document + */ + private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMaster) 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) { + if (offsetInMaster == fragments[index].offset) + throw new IllegalArgumentException("overlaps with existing fragment"); + if (offsetInMaster + lengthInMaster == fragments[index].offset) + right= (Fragment) fragments[index]; + } + + if (0 < index && index <= fragments.length) { + Fragment fragment= (Fragment) fragments[index - 1]; + if (fragment.includes(offsetInMaster)) + throw new IllegalArgumentException("overlaps with existing fragment"); + if (fragment.getOffset() + fragment.getLength() == offsetInMaster) + left= fragment; + } + + // 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(); + } + + int offsetInSlave= fMapping.toImageOffset(offsetInMaster); + Assert.isTrue(offsetInSlave != -1); + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, offsetInSlave, 0, fMasterDocument.get(offsetInMaster, lengthInMaster)); + super.fireDocumentAboutToBeChanged(event); + 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(); + + if (fragment.getOffset() == offsetInMaster) { + fragment.setOffset(offsetInMaster + lengthInMaster); + fragment.setLength(fragment.getLength() - lengthInMaster); + } else if (fragment.getOffset() + fragment.getLength() == 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()); + } + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), null); + super.fireDocumentAboutToBeChanged(event); + getTracker().replace(event.getOffset(), event.getLength(), event.getText()); + super.fireDocumentChanged(event); + + } catch (BadPositionCategoryException x) { + internalError(); + } + } + + private 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 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; + } + + /** + * 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 + */ + public void addMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException { + + IRegion[] gaps= computeUnprojectedMasterRegions(offsetInMaster, lengthInMaster); + if (gaps == null) + return; + + for (int i= 0; i < gaps.length; i++) { + IRegion gap= gaps[i]; + internalAddMasterDocumentRange(gap.getOffset(), gap.getLength()); + } + } + + /** + * 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 + */ + public void removeMasterDocumentRange(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; + + for (int i= 0; i < fragments.length; i++) { + IRegion fragment= fragments[i]; + internalRemoveMasterDocumentRange(fragment.getOffset(), fragment.getLength()); + } + } + + /* + * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String) + */ + 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(); + } + } + + /* + * @see org.eclipse.jface.text.IDocument#set(java.lang.String) + */ + 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 + */ + private ProjectionDocumentEvent normalize(DocumentEvent masterEvent) throws BadLocationException { + 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; + } + + /** + * Ensures that when the master event effects 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 + */ + private boolean adaptProjectionToMasterChange(DocumentEvent masterEvent) throws BadLocationException { + if (!fIsUpdating || fIsAutoExpanding) { + IRegion region= new Region(masterEvent.getOffset(), masterEvent.getLength()); + IRegion exactImage= fMapping.toExactImageRegion(region); + IRegion image= fMapping.toImageRegion(region); + if (exactImage == null && image != null) { + addMasterDocumentRange(masterEvent.getOffset(), masterEvent.getLength()); + return true; + } + } + return false; + } + + /** + * 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 ( !fIsUpdating && masterEvent == fMasterEvent) { + if (fSlaveEvent != null) { + try { + getTracker().replace(fSlaveEvent.getOffset(), fSlaveEvent.getLength(), fSlaveEvent.getText()); + fireDocumentChanged(fSlaveEvent); + } catch (BadLocationException e) { + internalError(); + } + } else { + ensureWellFormedSegmentation(); + } + } + } + + /* + * @see org.eclipse.jface.text.AbstractDocument#fireDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) + */ + protected void fireDocumentAboutToBeChanged(DocumentEvent 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 + */ + protected void fireDocumentChanged(DocumentEvent event) { + super.fireDocumentChanged(fSlaveEvent); + } + + /* + * @see org.eclipse.jface.text.AbstractDocument#updateDocumentStructures(org.eclipse.jface.text.DocumentEvent) + */ + protected void updateDocumentStructures(DocumentEvent event) { + super.updateDocumentStructures(event); + ensureWellFormedSegmentation(); + } + + private void ensureWellFormedSegmentation() { + Position[] segments= getSegments(); + for (int i= 0; i < segments.length; i++) { + Segment segment= (Segment) segments[i]; + if (segment.isDeleted()) { + try { + removePosition(fSegmentsCategory, segment); + fMasterDocument.removePosition(fFragmentsCategory, segment.fragment); + } catch (BadPositionCategoryException e) { + internalError(); + } + } else if (i < segments.length - 1) { + Segment next= (Segment) segments[i + 1]; + if (next.isDeleted()) + 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(); + } + } + } + } + + /* + * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace) + */ + public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + if (!fIsUpdating) + throw new UnsupportedOperationException(); + super.registerPostNotificationReplace(owner, replace); + } + + /** + * Sets the auto expand mode for this document. + */ + public void setAutoExpandMode(boolean autoExpandMode) { + fIsAutoExpanding= autoExpandMode; + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentEvent.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentEvent.java new file mode 100644 index 00000000000..f443d51773c --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentEvent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.SlaveDocumentEvent; + +/** + * Internal class. Do not use. Only public for testing purposes. + * + * @since 3.0 + */ +public class ProjectionDocumentEvent extends SlaveDocumentEvent { + + public final static Object PROJECTION_CHANGE= new Object(); + public final static Object CONTENT_CHANGE= new Object(); + + private Object fChangeType; + + public ProjectionDocumentEvent(IDocument doc, int offset, int length, String text, DocumentEvent masterEvent) { + super(doc, offset, length, text, masterEvent); + fChangeType= CONTENT_CHANGE; + } + + public ProjectionDocumentEvent(IDocument doc, int offset, int length, String text) { + super(doc, offset, length, text, null); + fChangeType= PROJECTION_CHANGE; + } + + public Object getChangeType() { + return fChangeType; + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentManager.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentManager.java new file mode 100644 index 00000000000..856753f2b7d --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocumentManager.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentInformationMapping; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.ISlaveDocumentManager; + + + +/** + * <code>ProjectionDocumentManager</code> is one particular implementation of + * <code>ISlaveDocumentManager</code>. This manager creates so called + * projection documents as slave documents for given master documents. + * <p> + * A projection document represents a particular projection of the master + * document and is accordingly adapted to changes of the master document. Vice + * versa, the master document is accordingly adapted to changes of its slave + * documents. The manager does not maintain any particular management structure + * but utilizes mechanisms given by <code>IDocument</code> such as position + * categories and position updaters. + * <p> + * API in progress. Do not yet use. + * + * @since 3.0 + */ +public class ProjectionDocumentManager implements IDocumentListener, ISlaveDocumentManager { + + + /** + * Name of the position category used to keep track of the master + * document's fragments that correspond to the segments of the projection + * documents. + */ + protected final static String FRAGMENTS_CATEGORY= "__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 + * documents. + */ + protected final static String SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$ + + + /** The position updater shared by all master documents which have projection documents */ + private IPositionUpdater fFragmentsUpdater; + /** Registry for master documents and their projection documents. */ + private Map fRegistry= new HashMap(); + + + /** + * Returns the fragments updater. If necessary, it is dynamically created. + * + * @return the fragments updater + */ + private IPositionUpdater getFragmentsUpdater() { + if (fFragmentsUpdater == null) + fFragmentsUpdater= new FragmentUpdater(FRAGMENTS_CATEGORY); + return fFragmentsUpdater; + } + + /** + * Registers the given projection document for the given master document. + * + * @param master the master document + * @param projection the projection document + */ + private void add(IDocument master, ProjectionDocument projection) { + List list= (List) fRegistry.get(master); + if (list == null) { + list= new ArrayList(1); + fRegistry.put(master, list); + } + list.add(projection); + } + + /** + * Unregisters the given projection document from its master. + * + * @param master the master document + * @param projection the projection document + */ + private void remove(IDocument master, ProjectionDocument projection) { + List list= (List) fRegistry.get(master); + if (list != null) { + list.remove(projection); + if (list.size() == 0) + fRegistry.remove(master); + } + } + + /** + * Returns whether the given document is a master document. + * + * @param master the document + * @return <code>true</code> if the given document is a master document known to this manager + */ + private boolean hasProjection(IDocument master) { + return (fRegistry.get(master) instanceof List); + } + + /** + * Returns an iterator enumerating all projection documents registered for the given document or + * <code>null</code> if the document is not a known master document. + * + * @param master the document + * @return an iterator for all registered projection documents or <code>null</code> + */ + private Iterator getProjectionsIterator(IDocument master) { + List list= (List) fRegistry.get(master); + if (list != null) + return list.iterator(); + return null; + } + + /** + * Informs all projection documents of the master document that issued the given document event. + * + * @param about indicates whether the change is about to happen or happened already + * @param masterEvent the document event which will be processed to inform the projection documents + */ + protected void fireDocumentEvent(boolean about, DocumentEvent masterEvent) { + IDocument master= masterEvent.getDocument(); + Iterator e= getProjectionsIterator(master); + if (e == null) + return; + + while (e.hasNext()) { + ProjectionDocument document= (ProjectionDocument) e.next(); + if (about) + document.masterDocumentAboutToBeChanged(masterEvent); + else + document.masterDocumentChanged(masterEvent); + } + } + + /* + * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent) + */ + public void documentChanged(DocumentEvent event) { + fireDocumentEvent(false, event); + } + + /* + * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent event) { + fireDocumentEvent(true, event); + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#createMasterSlaveMapping(org.eclipse.jface.text.IDocument) + */ + public IDocumentInformationMapping createMasterSlaveMapping(IDocument slave) { + if (slave instanceof ProjectionDocument) { + ProjectionDocument projectionDocument= (ProjectionDocument) slave; + return projectionDocument.getProjectionMapping(); + } + return null; + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#createSlaveDocument(org.eclipse.jface.text.IDocument) + */ + public IDocument createSlaveDocument(IDocument master) { + if (!master.containsPositionCategory(FRAGMENTS_CATEGORY)) { + master.addPositionCategory(FRAGMENTS_CATEGORY); + master.addPositionUpdater(getFragmentsUpdater()); + master.addDocumentListener(this); + } + ProjectionDocument slave= createProjectionDocument(master); + add(master, slave); + return slave; + } + + /** + * Factory method for projection documents. + * + * @param master the master document + * @return the newly created projection document + */ + protected ProjectionDocument createProjectionDocument(IDocument master) { + return new ProjectionDocument(master, FRAGMENTS_CATEGORY, SEGMENTS_CATEGORY); + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#freeSlaveDocument(org.eclipse.jface.text.IDocument) + */ + public void freeSlaveDocument(IDocument slave) { + if (slave instanceof ProjectionDocument) { + ProjectionDocument projectionDocument= (ProjectionDocument) slave; + IDocument master= projectionDocument.getMasterDocument(); + remove(master, projectionDocument); + + try { + if (!hasProjection(master)) { + master.removeDocumentListener(this); + master.removePositionUpdater(getFragmentsUpdater()); + master.removePositionCategory(FRAGMENTS_CATEGORY); + } + } catch (BadPositionCategoryException x) { + } + } + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#getMasterDocument(org.eclipse.jface.text.IDocument) + */ + public IDocument getMasterDocument(IDocument slave) { + if (slave instanceof ProjectionDocument) + return ((ProjectionDocument) slave).getMasterDocument(); + return null; + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#isSlaveDocument(org.eclipse.jface.text.IDocument) + */ + public boolean isSlaveDocument(IDocument document) { + return (document instanceof ProjectionDocument); + } + + /* + * @see org.eclipse.jface.text.ISlaveDocumentManager#setAutoExpandMode(org.eclipse.jface.text.IDocument, boolean) + */ + public void setAutoExpandMode(IDocument slave, boolean autoExpanding) { + if (slave instanceof ProjectionDocument) + ((ProjectionDocument) slave).setAutoExpandMode(autoExpanding); + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionMapping.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionMapping.java new file mode 100644 index 00000000000..07ca37ddddc --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionMapping.java @@ -0,0 +1,467 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentInformationMapping; +import org.eclipse.jface.text.IDocumentInformationMappingExtension; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; + +/** + * Internal class. Do not use. Only public for testing purposes. + * <p> + * Implementation of <code>IDocumentInformationMapping</code> for the + * projection mapping between a master and a slave document. + * + * @since 3.0 + */ +public class ProjectionMapping implements IDocumentInformationMapping , IDocumentInformationMappingExtension, IMinimalMapping { + + private static final int LEFT= -1; + private static final int NONE= 0; + private static final int RIGHT= +1; + + /** The master document */ + private IDocument fMasterDocument; + /** The position category used to manage the projection fragments inside the master document */ + private String fFragmentsCategory; + /** The projection document */ + private IDocument fSlaveDocument; + /** The position category to manage the projection segments inside the slave document. */ + private String fSegmentsCategory; + + + /** + * Creates a new mapping between the given parent document and the given projection document. + * + * @param masterDocument the master document + * @param fragmentsCategory the position category of the parent document used to manage the projected regions + * @param slaveDocument the slave document + * @param segmentsCategory the position category of the projection document used to manage the fragments + */ + public ProjectionMapping(IDocument masterDocument, String fragmentsCategory, IDocument slaveDocument, String segmentsCategory) { + fMasterDocument= masterDocument; + fFragmentsCategory= fragmentsCategory; + fSlaveDocument= slaveDocument; + fSegmentsCategory= segmentsCategory; + } + + private Position[] getSegments() { + try { + return fSlaveDocument.getPositions(fSegmentsCategory); + } catch (BadPositionCategoryException e) { + } + + return new Position[0]; + } + + private Position[] getFragments() { + try { + return fMasterDocument.getPositions(fFragmentsCategory); + } catch (BadPositionCategoryException e) { + } + + return new Position[0]; + } + + private int findSegmentIndex(int offset) throws BadLocationException { + Position[] segments= getSegments(); + if (segments.length == 0) { + if (offset > 0) + throw new BadLocationException(); + return -1; + } + + try { + int index= fSlaveDocument.computeIndexInCategory(fSegmentsCategory, offset); + if (index == segments.length && offset > (segments[index-1].offset + segments[index-1].length)) + throw new BadLocationException(); + + if (index < segments.length && offset == segments[index].offset) + return index; + + if (index > 0) + index--; + + return index; + + } catch (BadPositionCategoryException e) { + throw new IllegalStateException(); + } + } + + private Segment findSegment(int offset) throws BadLocationException { + + if (offset < 0 || getImageLength() < offset) + throw new BadLocationException(); + + int index= findSegmentIndex(offset); + if (index == -1) { + + Segment s= new Segment(0, 0); + Fragment f= new Fragment(0, 0); + s.fragment= f; + f.segment= s; + return s; + } + + Position[] segments= getSegments(); + return (Segment) segments[index]; + } + + private int findFragmentIndex(int offset, int extensionDirection) throws BadLocationException { + try { + + Position[] fragments= getFragments(); + if (fragments.length == 0) + return -1; + + int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, offset); + + if (index < fragments.length && offset == fragments[index].offset) + return index; + + if (0 < index && index <= fragments.length && fragments[index - 1].includes(offset)) + return index - 1; + + switch(extensionDirection) { + case LEFT: + return Math.max(index - 1, 0); + case RIGHT: + return Math.min(index, fragments.length - 1); + } + + return -1; + + } catch (BadPositionCategoryException e) { + throw new IllegalStateException(); + } + } + + private Fragment findFragment(int offset) throws BadLocationException { + + int length= fMasterDocument.getLength(); + if (offset < 0 || length < offset) + throw new BadLocationException(); + + int index= findFragmentIndex(offset, NONE); + Position[] fragments= getFragments(); + if (index == -1) { + if (fragments.length > 0) { + Fragment last= (Fragment) fragments[fragments.length - 1]; + if (last.getOffset() + last.getLength() == offset) + return last; + } + return null; + } + return (Fragment) fragments[index]; + } + + private Fragment[] findFragments(IRegion region, boolean exact) throws BadLocationException { + int offset= region.getOffset(); + if (offset < 0 || fMasterDocument.getLength() < offset) + throw new BadLocationException(); + + int inclusiveEndOffset= region.getOffset() + region.getLength() - 1; + if (inclusiveEndOffset < 0 || fMasterDocument.getLength() < inclusiveEndOffset) + throw new BadLocationException(); + + int startIndex= findFragmentIndex(offset, exact ? NONE : LEFT); + if (startIndex == -1) + return new Fragment[0]; + + int endIndex= findFragmentIndex(inclusiveEndOffset, exact ? NONE : RIGHT); + if (endIndex == -1) + return new Fragment[0]; + + Position[] fragments= getFragments(); + while (startIndex <= endIndex && !fragments[startIndex].overlapsWith(region.getOffset(), region.getLength())) + ++startIndex; + + while (endIndex >= startIndex && !fragments[endIndex].overlapsWith(region.getOffset(), region.getLength())) + --endIndex; + + int length= Math.max(0, endIndex - startIndex + 1); + Fragment[] result= new Fragment[length]; + for (int i= 0; i < length; i++) + result[i]= (Fragment) fragments[startIndex + i]; + return result; + } + + private IRegion toImageRegion(IRegion originRegion, boolean exact) throws BadLocationException { + if (originRegion.getLength() == 0) { + int imageOffset= toImageOffset(originRegion.getOffset()); + return imageOffset == -1 ? null : new Region(imageOffset, 0); + } + + Fragment[] fragments= findFragments(originRegion, exact); + if (fragments.length > 0) { + + // translate start offset + Fragment fragment= fragments[0]; + int originOffset= originRegion.getOffset(); + int relative= originOffset - fragment.getOffset(); + if (relative < 0) { + Assert.isTrue(!exact); + relative= 0; + } + int imageOffset= fragment.segment.getOffset() + relative; + + // translate end offset + fragment= fragments[fragments.length - 1]; + int exclusiveOriginEndOffset= originRegion.getOffset() + originRegion.getLength(); + relative= exclusiveOriginEndOffset - fragment.getOffset(); + if (relative > fragment.getLength()) { + Assert.isTrue(!exact); + int delta= relative - fragment.getLength(); + relative -= delta; + } + int exclusiveImageEndOffset= fragment.segment.getOffset() + relative; + + return new Region(imageOffset, exclusiveImageEndOffset - imageOffset); + } + + return null; + } + + private IRegion createOriginStartRegion(Segment original, int offsetShift) { + return new Region(original.fragment.getOffset() + offsetShift, original.fragment.getLength() - offsetShift); + } + + private IRegion createOriginRegion(Segment origin) { + return new Region(origin.fragment.getOffset(), origin.fragment.getLength()); + } + + private IRegion createOriginEndRegion(Segment original, int lengthReduction) { + return new Region(original.fragment.getOffset(), original.fragment.getLength() - lengthReduction); + } + + private IRegion getIntersectingRegion(IRegion left, IRegion right) { + int offset= Math.max(left.getOffset(), right.getOffset()); + int exclusiveEndOffset= Math.min(left.getOffset() + left.getLength(), right.getOffset() + right.getLength()); + if (exclusiveEndOffset < offset) + return null; + return new Region(offset, exclusiveEndOffset - offset); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#getCoverage() + */ + public IRegion getCoverage() { + Position[] fragments= getFragments(); + if (fragments != null && fragments.length > 0) { + Position first=fragments[0]; + Position last= fragments[fragments.length -1]; + return new Region(first.offset, (last.offset + last.length) - first.offset); + } + return new Region(0, 0); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toOriginOffset(int) + */ + public int toOriginOffset(int imageOffset) throws BadLocationException { + Segment segment= findSegment(imageOffset); + int relative= imageOffset - segment.offset; + return segment.fragment.offset + relative; + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toOriginRegion(org.eclipse.jface.text.IRegion) + */ + public IRegion toOriginRegion(IRegion imageRegion) throws BadLocationException { + int imageOffset= imageRegion.getOffset(); + int imageLength= imageRegion.getLength(); + + if (imageLength == 0) { + if (imageOffset == 0 && getImageLength() == 0) + return new Region(0, fMasterDocument.getLength()); + return new Region(toOriginOffset(imageOffset), 0); + } + + int originOffset= toOriginOffset(imageOffset); + int inclusiveImageEndOffset= imageOffset + imageLength -1; + int inclusiveOriginEndOffset= toOriginOffset(inclusiveImageEndOffset); + + return new Region(originOffset, (inclusiveOriginEndOffset + 1) - originOffset); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toOriginLines(int) + */ + public IRegion toOriginLines(int imageLine) throws BadLocationException { + IRegion imageRegion= fSlaveDocument.getLineInformation(imageLine); + IRegion originRegion= toOriginRegion(imageRegion); + + int originStartLine= fMasterDocument.getLineOfOffset(originRegion.getOffset()); + if (originRegion.getLength() == 0) + return new Region(originStartLine, 1); + + int inclusiveOriginEndOffset= originRegion.getOffset() + originRegion.getLength() -1; + int originEndLine= fMasterDocument.getLineOfOffset(inclusiveOriginEndOffset); + return new Region(originStartLine, (originEndLine + 1) - originStartLine); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toOriginLine(int) + */ + public int toOriginLine(int imageLine) throws BadLocationException { + IRegion lines= toOriginLines(imageLine); + return (lines.getLength() > 1 ? -1 : lines.getOffset()); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toImageOffset(int) + */ + public int toImageOffset(int originOffset) throws BadLocationException { + Fragment fragment= findFragment(originOffset); + if (fragment != null) { + int relative= originOffset - fragment.offset; + return fragment.segment.offset + relative; + } + return -1; + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMappingExtension#toExactImageRegion(org.eclipse.jface.text.IRegion) + */ + public IRegion toExactImageRegion(IRegion originRegion) throws BadLocationException { + return toImageRegion(originRegion, true); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toImageRegion(org.eclipse.jface.text.IRegion) + */ + public IRegion toImageRegion(IRegion originRegion) throws BadLocationException { + return toImageRegion(originRegion, false); + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toImageLine(int) + */ + public int toImageLine(int originLine) throws BadLocationException { + IRegion originRegion= fMasterDocument.getLineInformation(originLine); + IRegion imageRegion= toImageRegion(originRegion); + if (imageRegion == null) { + int imageOffset= toImageOffset(originRegion.getOffset()); + if (imageOffset > -1) + imageRegion= new Region(imageOffset, 0); + else + return -1; + } + + int startLine= fSlaveDocument.getLineOfOffset(imageRegion.getOffset()); + if (imageRegion.getLength() == 0) + return startLine; + + int endLine= fSlaveDocument.getLineOfOffset(imageRegion.getOffset() + imageRegion.getLength()); + if (endLine != startLine) + throw new IllegalStateException(); + + return startLine; + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMapping#toClosestImageLine(int) + */ + public int toClosestImageLine(int originLine) throws BadLocationException { + try { + + int imageLine= toImageLine(originLine); + if (imageLine > -1) + return imageLine; + + Position[] fragments= getFragments(); + if (fragments.length == 0) + return -1; + + IRegion originLineRegion= fMasterDocument.getLineInformation(originLine); + int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, originLineRegion.getOffset()); + + if (0 < index && index < fragments.length) { + Fragment left= (Fragment) fragments[index - 1]; + int leftDistance= originLineRegion.getOffset() - (left.getOffset() + left.getLength()); + Fragment right= (Fragment) fragments[index]; + int rightDistance= right.getOffset() - (originLineRegion.getOffset() + originLineRegion.getLength()); + + if (leftDistance <= rightDistance) + originLine= fMasterDocument.getLineOfOffset(left.getOffset() + Math.max(left.getLength() - 1, 0)); + else + originLine= fMasterDocument.getLineOfOffset(right.getOffset()); + + } else if (index == 0) { + Fragment right= (Fragment) fragments[index]; + originLine= fMasterDocument.getLineOfOffset(right.getOffset()); + } else if (index == fragments.length) { + Fragment left= (Fragment) fragments[index - 1]; + originLine= fMasterDocument.getLineOfOffset(left.getOffset() + left.getLength()); + } + + return toImageLine(originLine); + + } catch (BadPositionCategoryException x) { + } + + return -1; + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMappingExtension#toExactOriginRegions(org.eclipse.jface.text.IRegion) + */ + public IRegion[] toExactOriginRegions(IRegion imageRegion) throws BadLocationException { + + if (imageRegion.getLength() == 0) + return new IRegion[] { new Region(toOriginOffset(imageRegion.getOffset()), 0) }; + + int endOffset= imageRegion.getOffset() + imageRegion.getLength(); + Position[] segments= getSegments(); + int firstIndex= findSegmentIndex(imageRegion.getOffset()); + int lastIndex= findSegmentIndex(endOffset - 1); + + int resultLength= lastIndex - firstIndex + 1; + IRegion[] result= new IRegion[resultLength]; + + // first + result[0]= createOriginStartRegion((Segment) segments[firstIndex], imageRegion.getOffset() - segments[firstIndex].getOffset()); + // middles + for (int i= 1; i < resultLength - 1; i++) + result[i]= createOriginRegion((Segment) segments[firstIndex + i]); + // last + Segment last= (Segment) segments[lastIndex]; + int segmentEndOffset= last.getOffset() + last.getLength(); + IRegion lastRegion= createOriginEndRegion(last, segmentEndOffset - endOffset); + if (resultLength > 1) { + // first != last + result[resultLength - 1]= lastRegion; + } else { + // merge first and last + result[0]= getIntersectingRegion(result[0], lastRegion); + } + + return result; + } + + /* + * @see org.eclipse.jface.text.IDocumentInformationMappingExtension#getImageLength() + */ + public int getImageLength() { + Position[] segments= getSegments(); + int length= 0; + for (int i= 0; i < segments.length; i++) + length += segments[i].length; + return length; + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionTextStore.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionTextStore.java new file mode 100644 index 00000000000..8a6d492f5e8 --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionTextStore.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextStore; +import org.eclipse.jface.text.Region; + +/** + * * Internal class. Do not use. Only public for testing purposes. + * <p> + * A text store representing the projection defined by the given document + * information mapping. + * + * @since 3.0 + */ +public class ProjectionTextStore implements ITextStore { + + /** + * Implementation of <code>IRegion</code> that can be reused + * by setting the offset and the length. + */ + private static class ReusableRegion implements IRegion { + + private int fOffset; + private int fLength; + + /* + * @see org.eclipse.jface.text.IRegion#getLength() + */ + public int getLength() { + return fLength; + } + + /* + * @see org.eclipse.jface.text.IRegion#getOffset() + */ + public int getOffset() { + return fOffset; + } + + /** + * Updates this region. + * + * @param offset the new offset + * @param length the new length + */ + public void update(int offset, int length) { + fOffset= offset; + fLength= length; + } + } + + /** The master document */ + private IDocument fMasterDocument; + /** The document information mapping */ + private IMinimalMapping fMapping; + /** Internal region used for querying the mapping. */ + private ReusableRegion fReusableRegion= new ReusableRegion(); + + + /** + * Creates a new projection text store for the given master document and + * the given document information mapping. + * + * @param masterDocument the master document + * @param mapping the document information mapping + */ + public ProjectionTextStore(IDocument masterDocument, IMinimalMapping mapping) { + fMasterDocument= masterDocument; + fMapping= mapping; + } + + private void internalError() { + throw new IllegalStateException(); + } + + /* + * @see org.eclipse.jface.text.ITextStore#set(java.lang.String) + */ + public void set(String contents) { + + IRegion masterRegion= fMapping.getCoverage(); + if (masterRegion == null) + internalError(); + + try { + fMasterDocument.replace(masterRegion.getOffset(), masterRegion.getLength(), contents); + } catch (BadLocationException e) { + internalError(); + } + } + + /* + * @see org.eclipse.jface.text.ITextStore#replace(int, int, java.lang.String) + */ + public void replace(int offset, int length, String text) { + fReusableRegion.update(offset, length); + try { + IRegion masterRegion= fMapping.toOriginRegion(fReusableRegion); + fMasterDocument.replace(masterRegion.getOffset(), masterRegion.getLength(), text); + } catch (BadLocationException e) { + internalError(); + } + } + + /* + * @see org.eclipse.jface.text.ITextStore#getLength() + */ + public int getLength() { + return fMapping.getImageLength(); + } + + /* + * @see org.eclipse.jface.text.ITextStore#get(int) + */ + public char get(int offset) { + try { + int originOffset= fMapping.toOriginOffset(offset); + return fMasterDocument.getChar(originOffset); + } catch (BadLocationException e) { + internalError(); + } + + // unreachable + return (char) 0; + } + + /* + * @see ITextStore#get(int, int) + */ + public String get(int offset, int length) { + try { + IRegion[] fragments= fMapping.toExactOriginRegions(new Region(offset, length)); + StringBuffer buffer= new StringBuffer(); + for (int i= 0; i < fragments.length; i++) { + IRegion fragment= fragments[i]; + buffer.append(fMasterDocument.get(fragment.getOffset(), fragment.getLength())); + } + return buffer.toString(); + } catch (BadLocationException e) { + internalError(); + } + + // unreachable + return null; + } +} diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/Segment.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/Segment.java new file mode 100644 index 00000000000..febbc27252b --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/Segment.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.Position; + +/** + * Internal class. Do not use. Only public for testing purposes. + * + * @since 3.0 + */ +public class Segment extends Position { + + public Fragment fragment; + public boolean isMarkedForStretch; + public boolean isMarkedForShift; + + public Segment(int offset, int length) { + super(offset, length); + } + + public void markForStretch() { + isMarkedForStretch= true; + } + + public boolean isMarkedForStretch() { + return isMarkedForStretch; + } + + public void markForShift() { + isMarkedForShift= true; + } + + public boolean isMarkedForShift() { + return isMarkedForShift; + } + + public void clearMark() { + isMarkedForStretch= false; + isMarkedForShift= false; + } +}
\ No newline at end of file diff --git a/org.eclipse.text/projection/org/eclipse/jface/text/projection/SegmentUpdater.java b/org.eclipse.text/projection/org/eclipse/jface/text/projection/SegmentUpdater.java new file mode 100644 index 00000000000..cb242e32efa --- /dev/null +++ b/org.eclipse.text/projection/org/eclipse/jface/text/projection/SegmentUpdater.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.projection; + +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DefaultPositionUpdater; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.Position; + + +/** + * Internal class. Do not use. Only public for testing purposes. + * <p> + * The position updater used to adapt the segments of a projection document to + * changes of the master document. If an insertion happens at a segment's + * offset, the segment is extended rather than shifted. Also, the last segment + * is extended if an insert operation happens at the end of the segment. + * + * @since 3.0 + */ +public class SegmentUpdater extends DefaultPositionUpdater { + + private Segment fNextSegment= null; + private boolean fIsProjectionChange= false; + + /** + * Creates the segment updater for the given category. + * + * @param segmentCategory the position category used for managing the segments of a projection document + */ + protected SegmentUpdater(String segmentCategory) { + super(segmentCategory); + } + + /* + * @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent) + */ + public void update(DocumentEvent event) { + + Assert.isTrue(event instanceof ProjectionDocumentEvent); + fIsProjectionChange= ((ProjectionDocumentEvent) event).getChangeType() == ProjectionDocumentEvent.PROJECTION_CHANGE; + + try { + + Position[] category= event.getDocument().getPositions(getCategory()); + + fOffset= event.getOffset(); + fLength= event.getLength(); + fReplaceLength= (event.getText() == null ? 0 : event.getText().length()); + fDocument= event.getDocument(); + + for (int i= 0; i < category.length; i++) { + + fPosition= category[i]; + Assert.isTrue(fPosition instanceof Segment); + + if (i < category.length - 1) + fNextSegment= (Segment) category[i + 1]; + else + fNextSegment= null; + + fOriginalPosition.offset= fPosition.offset; + fOriginalPosition.length= fPosition.length; + + if (notDeleted()) + adaptToReplace(); + + if (fPosition.getLength() == 0) + fPosition.isDeleted= true; + } + + } catch (BadPositionCategoryException x) { + // do nothing + } + } + + /* + * @see org.eclipse.jface.text.DefaultPositionUpdater#adaptToInsert() + */ + protected void adaptToInsert() { + + Segment segment= (Segment) fPosition; + int myStart= segment.offset; + int myEnd= segment.offset + segment.length - (segment.isMarkedForStretch || fNextSegment == null || isAffectingReplace() ? 0 : 1); + myEnd= Math.max(myStart, myEnd); + int yoursStart= fOffset; + + try { + + if (myEnd < yoursStart) + return; + + if (segment.isMarkedForStretch) { + Assert.isTrue(fIsProjectionChange); + segment.isMarkedForShift= false; + if (fNextSegment != null) { + fNextSegment.isMarkedForShift= true; + fNextSegment.isMarkedForStretch= false; + } + } + + if (fLength <= 0) { + + if (myStart < (yoursStart + (segment.isMarkedForShift ? 0 : 1))) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + + } else { + + if (myStart <= yoursStart && fOriginalPosition.offset <= yoursStart) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + } + + } finally { + segment.clearMark(); + } + } +} |