001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text;
011:
012: /**
013: * Default implementation of {@link org.eclipse.jface.text.IPositionUpdater}.
014: * <p>
015: * A default position updater must be configured with the position category whose positions it will
016: * update. Other position categories are not affected by this updater.
017: * </p>
018: * <p>
019: * This implementation follows the specification below:
020: * </p>
021: * <ul>
022: * <li>Inserting or deleting text before the position shifts the position accordingly.</li>
023: * <li>Inserting text at the position offset shifts the position accordingly.</li>
024: * <li>Inserting or deleting text strictly contained by the position shrinks or stretches the
025: * position.</li>
026: * <li>Inserting or deleting text after a position does not affect the position.</li>
027: * <li>Deleting text which strictly contains the position deletes the position. Note that the
028: * position is not deleted if its only shrunken to length zero. To delete a position, the
029: * modification must delete from <i>strictly before</i> to <i>strictly after</i> the position.</li>
030: * <li>Replacing text overlapping with the position is considered as a sequence of first deleting
031: * the replaced text and afterwards inserting the new text. Thus, a position might first be shifted
032: * and shrunken and then be stretched.</li>
033: * </ul>
034: * This class can be used as is or be adapted by subclasses. Fields are protected to allow
035: * subclasses direct access. Because of the frequency with which position updaters are used this is
036: * a performance decision.
037: */
038: public class DefaultPositionUpdater implements IPositionUpdater {
039:
040: /** The position category the updater draws responsible for */
041: private String fCategory;
042:
043: /** Caches the currently investigated position */
044: protected Position fPosition;
045: /**
046: * Remembers the original state of the investigated position
047: * @since 2.1
048: */
049: protected Position fOriginalPosition = new Position(0, 0);
050: /** Caches the offset of the replaced text */
051: protected int fOffset;
052: /** Caches the length of the replaced text */
053: protected int fLength;
054: /** Caches the length of the newly inserted text */
055: protected int fReplaceLength;
056: /** Catches the document */
057: protected IDocument fDocument;
058:
059: /**
060: * Creates a new default position updater for the given category.
061: *
062: * @param category the category the updater is responsible for
063: */
064: public DefaultPositionUpdater(String category) {
065: fCategory = category;
066: }
067:
068: /**
069: * Returns the category this updater is responsible for.
070: *
071: * @return the category this updater is responsible for
072: */
073: protected String getCategory() {
074: return fCategory;
075: }
076:
077: /**
078: * Returns whether the current event describes a well formed replace
079: * by which the current position is directly affected.
080: *
081: * @return <code>true</code> the current position is directly affected
082: * @since 3.0
083: */
084: protected boolean isAffectingReplace() {
085: return fLength > 0 && fReplaceLength > 0
086: && fPosition.length < fOriginalPosition.length;
087: }
088:
089: /**
090: * Adapts the currently investigated position to an insertion.
091: */
092: protected void adaptToInsert() {
093:
094: int myStart = fPosition.offset;
095: int myEnd = fPosition.offset + fPosition.length - 1;
096: myEnd = Math.max(myStart, myEnd);
097:
098: int yoursStart = fOffset;
099: int yoursEnd = fOffset + fReplaceLength - 1;
100: yoursEnd = Math.max(yoursStart, yoursEnd);
101:
102: if (myEnd < yoursStart)
103: return;
104:
105: if (fLength <= 0) {
106:
107: if (myStart < yoursStart)
108: fPosition.length += fReplaceLength;
109: else
110: fPosition.offset += fReplaceLength;
111:
112: } else {
113:
114: if (myStart <= yoursStart
115: && fOriginalPosition.offset <= yoursStart)
116: fPosition.length += fReplaceLength;
117: else
118: fPosition.offset += fReplaceLength;
119: }
120: }
121:
122: /**
123: * Adapts the currently investigated position to a deletion.
124: */
125: protected void adaptToRemove() {
126:
127: int myStart = fPosition.offset;
128: int myEnd = fPosition.offset + fPosition.length - 1;
129: myEnd = Math.max(myStart, myEnd);
130:
131: int yoursStart = fOffset;
132: int yoursEnd = fOffset + fLength - 1;
133: yoursEnd = Math.max(yoursStart, yoursEnd);
134:
135: if (myEnd < yoursStart)
136: return;
137:
138: if (myStart <= yoursStart) {
139:
140: if (yoursEnd <= myEnd)
141: fPosition.length -= fLength;
142: else
143: fPosition.length -= (myEnd - yoursStart + 1);
144:
145: } else if (yoursStart < myStart) {
146:
147: if (yoursEnd < myStart)
148: fPosition.offset -= fLength;
149: else {
150: fPosition.offset -= (myStart - yoursStart);
151: fPosition.length -= (yoursEnd - myStart + 1);
152: }
153:
154: }
155:
156: // validate position to allowed values
157: if (fPosition.offset < 0)
158: fPosition.offset = 0;
159:
160: if (fPosition.length < 0)
161: fPosition.length = 0;
162: }
163:
164: /**
165: * Adapts the currently investigated position to the replace operation.
166: * First it checks whether the change replaces the whole range of the position.
167: * If not, it performs first the deletion of the previous text and afterwards
168: * the insertion of the new text.
169: */
170: protected void adaptToReplace() {
171:
172: if (fPosition.offset == fOffset && fPosition.length == fLength
173: && fPosition.length > 0) {
174:
175: // replace the whole range of the position
176: fPosition.length += (fReplaceLength - fLength);
177: if (fPosition.length < 0) {
178: fPosition.offset += fPosition.length;
179: fPosition.length = 0;
180: }
181:
182: } else {
183:
184: if (fLength > 0)
185: adaptToRemove();
186:
187: if (fReplaceLength > 0)
188: adaptToInsert();
189: }
190: }
191:
192: /**
193: * Determines whether the currently investigated position has been deleted by
194: * the replace operation specified in the current event. If so, it deletes
195: * the position and removes it from the document's position category.
196: *
197: * @return <code>true</code> if position has not been deleted
198: */
199: protected boolean notDeleted() {
200:
201: if (fOffset < fPosition.offset
202: && (fPosition.offset + fPosition.length < fOffset
203: + fLength)) {
204:
205: fPosition.delete();
206:
207: try {
208: fDocument.removePosition(fCategory, fPosition);
209: } catch (BadPositionCategoryException x) {
210: }
211:
212: return false;
213: }
214:
215: return true;
216: }
217:
218: /*
219: * @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent)
220: */
221: public void update(DocumentEvent event) {
222:
223: try {
224:
225: fOffset = event.getOffset();
226: fLength = event.getLength();
227: fReplaceLength = (event.getText() == null ? 0 : event
228: .getText().length());
229: fDocument = event.getDocument();
230:
231: Position[] category = fDocument.getPositions(fCategory);
232: for (int i = 0; i < category.length; i++) {
233:
234: fPosition = category[i];
235: fOriginalPosition.offset = fPosition.offset;
236: fOriginalPosition.length = fPosition.length;
237:
238: if (notDeleted())
239: adaptToReplace();
240: }
241:
242: } catch (BadPositionCategoryException x) {
243: // do nothing
244: } finally {
245: fDocument = null;
246: }
247: }
248: }
|