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.formatter;
011:
012: import java.util.HashMap;
013: import java.util.Map;
014:
015: import org.eclipse.core.runtime.Assert;
016:
017: import org.eclipse.jface.text.BadLocationException;
018: import org.eclipse.jface.text.DefaultPositionUpdater;
019: import org.eclipse.jface.text.IDocument;
020: import org.eclipse.jface.text.IRegion;
021: import org.eclipse.jface.text.ITypedRegion;
022: import org.eclipse.jface.text.TextUtilities;
023: import org.eclipse.jface.text.TypedPosition;
024:
025: /**
026: * Content formatter for edit-based formatting strategies.
027: * <p>
028: * Two kinds of formatting strategies can be registered with this formatter:
029: * <ul>
030: * <li>one master formatting strategy for the default content type</li>
031: * <li>one formatting strategy for each non-default content type</li>
032: * </ul>
033: * The master formatting strategy always formats the whole region to be
034: * formatted in the first pass. In a second pass, all partitions of the region
035: * to be formatted that are not of master content type are formatted using the
036: * slave formatting strategy registered for the underlying content type. All
037: * formatting strategies must implement {@link IFormattingStrategyExtension}.
038: * <p>
039: * Regions to be formatted with the master formatting strategy always have
040: * an offset aligned to the line start. Regions to be formatted with slave formatting
041: * strategies are aligned on partition boundaries.
042: *
043: * @see IFormattingStrategyExtension
044: * @since 3.0
045: */
046: public class MultiPassContentFormatter implements IContentFormatter,
047: IContentFormatterExtension {
048:
049: /**
050: * Position updater that shifts otherwise deleted positions to the next
051: * non-whitespace character. The length of the positions are truncated to
052: * one if the position was shifted.
053: */
054: protected class NonDeletingPositionUpdater extends
055: DefaultPositionUpdater {
056:
057: /**
058: * Creates a new non-deleting position updater.
059: *
060: * @param category The position category to update its positions
061: */
062: public NonDeletingPositionUpdater(final String category) {
063: super (category);
064: }
065:
066: /*
067: * @see org.eclipse.jface.text.DefaultPositionUpdater#notDeleted()
068: */
069: protected final boolean notDeleted() {
070:
071: if (fOffset < fPosition.offset
072: && (fPosition.offset + fPosition.length < fOffset
073: + fLength)) {
074:
075: int offset = fOffset + fLength;
076: if (offset < fDocument.getLength()) {
077:
078: try {
079:
080: boolean moved = false;
081: char character = fDocument.getChar(offset);
082:
083: while (offset < fDocument.getLength()
084: && Character.isWhitespace(character)) {
085:
086: moved = true;
087: character = fDocument.getChar(offset++);
088: }
089:
090: if (moved)
091: offset--;
092:
093: } catch (BadLocationException exception) {
094: // Can not happen
095: }
096:
097: fPosition.offset = offset;
098: fPosition.length = 0;
099: }
100: }
101: return true;
102: }
103: }
104:
105: /** The master formatting strategy */
106: private IFormattingStrategyExtension fMaster = null;
107: /** The partitioning of this content formatter */
108: private final String fPartitioning;
109: /** The slave formatting strategies */
110: private final Map fSlaves = new HashMap();
111: /** The default content type */
112: private final String fType;
113:
114: /**
115: * Creates a new content formatter.
116: *
117: * @param partitioning the document partitioning for this formatter
118: * @param type the default content type
119: */
120: public MultiPassContentFormatter(final String partitioning,
121: final String type) {
122: fPartitioning = partitioning;
123: fType = type;
124: }
125:
126: /*
127: * @see org.eclipse.jface.text.formatter.IContentFormatterExtension#format(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.formatter.IFormattingContext)
128: */
129: public final void format(final IDocument medium,
130: final IFormattingContext context) {
131:
132: context.setProperty(FormattingContextProperties.CONTEXT_MEDIUM,
133: medium);
134:
135: final Boolean document = (Boolean) context
136: .getProperty(FormattingContextProperties.CONTEXT_DOCUMENT);
137: if (document == null || !document.booleanValue()) {
138:
139: final IRegion region = (IRegion) context
140: .getProperty(FormattingContextProperties.CONTEXT_REGION);
141: if (region != null) {
142: try {
143: formatMaster(context, medium, region.getOffset(),
144: region.getLength());
145: } finally {
146: formatSlaves(context, medium, region.getOffset(),
147: region.getLength());
148: }
149: }
150: } else {
151: try {
152: formatMaster(context, medium, 0, medium.getLength());
153: } finally {
154: formatSlaves(context, medium, 0, medium.getLength());
155: }
156: }
157: }
158:
159: /*
160: * @see org.eclipse.jface.text.formatter.IContentFormatter#format(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IRegion)
161: */
162: public final void format(final IDocument medium,
163: final IRegion region) {
164:
165: final FormattingContext context = new FormattingContext();
166:
167: context.setProperty(
168: FormattingContextProperties.CONTEXT_DOCUMENT,
169: Boolean.FALSE);
170: context.setProperty(FormattingContextProperties.CONTEXT_REGION,
171: region);
172:
173: format(medium, context);
174: }
175:
176: /**
177: * Formats the document specified in the formatting context with the master
178: * formatting strategy.
179: * <p>
180: * The master formatting strategy covers all regions of the document. The
181: * offset of the region to be formatted is aligned on line start boundaries,
182: * whereas the end index of the region remains the same. For this formatting
183: * type the document partitioning is not taken into account.
184: *
185: * @param context The formatting context to use
186: * @param document The document to operate on
187: * @param offset The offset of the region to format
188: * @param length The length of the region to format
189: */
190: protected void formatMaster(final IFormattingContext context,
191: final IDocument document, int offset, int length) {
192:
193: try {
194:
195: final int delta = offset
196: - document.getLineInformationOfOffset(offset)
197: .getOffset();
198: offset -= delta;
199: length += delta;
200:
201: } catch (BadLocationException exception) {
202: // Do nothing
203: }
204:
205: if (fMaster != null) {
206:
207: context.setProperty(
208: FormattingContextProperties.CONTEXT_PARTITION,
209: new TypedPosition(offset, length, fType));
210:
211: fMaster.formatterStarts(context);
212: fMaster.format();
213: fMaster.formatterStops();
214: }
215: }
216:
217: /**
218: * Formats the document specified in the formatting context with the
219: * formatting strategy registered for the content type.
220: * <p>
221: * For this formatting type only slave strategies are used. The region to be
222: * formatted is aligned on partition boundaries of the underlying content
223: * type. The exact formatting strategy is determined by the underlying
224: * content type of the document partitioning.
225: *
226: * @param context The formatting context to use
227: * @param document The document to operate on
228: * @param offset The offset of the region to format
229: * @param length The length of the region to format
230: * @param type The content type of the region to format
231: */
232: protected void formatSlave(final IFormattingContext context,
233: final IDocument document, final int offset,
234: final int length, final String type) {
235:
236: final IFormattingStrategyExtension strategy = (IFormattingStrategyExtension) fSlaves
237: .get(type);
238: if (strategy != null) {
239:
240: context.setProperty(
241: FormattingContextProperties.CONTEXT_PARTITION,
242: new TypedPosition(offset, length, type));
243:
244: strategy.formatterStarts(context);
245: strategy.format();
246: strategy.formatterStops();
247: }
248: }
249:
250: /**
251: * Formats the document specified in the formatting context with the slave
252: * formatting strategies.
253: * <p>
254: * For each content type of the region to be formatted in the document
255: * partitioning, the registered slave formatting strategy is used to format
256: * that particular region. The region to be formatted is aligned on
257: * partition boundaries of the underlying content type. If the content type
258: * is the document's default content type, nothing happens.
259: *
260: * @param context The formatting context to use
261: * @param document The document to operate on
262: * @param offset The offset of the region to format
263: * @param length The length of the region to format
264: */
265: protected void formatSlaves(final IFormattingContext context,
266: final IDocument document, final int offset, final int length) {
267:
268: Map partitioners = new HashMap(0);
269: try {
270:
271: final ITypedRegion[] partitions = TextUtilities
272: .computePartitioning(document, fPartitioning,
273: offset, length, false);
274:
275: if (!fType.equals(partitions[0].getType()))
276: partitions[0] = TextUtilities
277: .getPartition(document, fPartitioning,
278: partitions[0].getOffset(), false);
279:
280: if (partitions.length > 1) {
281:
282: if (!fType.equals(partitions[partitions.length - 1]
283: .getType()))
284: partitions[partitions.length - 1] = TextUtilities
285: .getPartition(document, fPartitioning,
286: partitions[partitions.length - 1]
287: .getOffset(), false);
288: }
289:
290: String type = null;
291: ITypedRegion partition = null;
292:
293: partitioners = TextUtilities
294: .removeDocumentPartitioners(document);
295:
296: for (int index = partitions.length - 1; index >= 0; index--) {
297:
298: partition = partitions[index];
299: type = partition.getType();
300:
301: if (!fType.equals(type))
302: formatSlave(context, document, partition
303: .getOffset(), partition.getLength(), type);
304: }
305:
306: } catch (BadLocationException exception) {
307: // Should not happen
308: } finally {
309: TextUtilities.addDocumentPartitioners(document,
310: partitioners);
311: }
312: }
313:
314: /*
315: * @see org.eclipse.jface.text.formatter.IContentFormatter#getFormattingStrategy(java.lang.String)
316: */
317: public final IFormattingStrategy getFormattingStrategy(
318: final String type) {
319: return null;
320: }
321:
322: /**
323: * Registers a master formatting strategy.
324: * <p>
325: * The strategy may already be registered with a certain content type as
326: * slave strategy. The master strategy is registered for the default content
327: * type of documents. If a master strategy has already been registered, it
328: * is overridden by the new one.
329: *
330: * @param strategy The master formatting strategy, must implement
331: * {@link IFormattingStrategyExtension}
332: */
333: public final void setMasterStrategy(
334: final IFormattingStrategy strategy) {
335: Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
336: fMaster = (IFormattingStrategyExtension) strategy;
337: }
338:
339: /**
340: * Registers a slave formatting strategy for a certain content type.
341: * <p>
342: * The strategy may already be registered as master strategy. An
343: * already registered slave strategy for the specified content type
344: * will be replaced. However, the same strategy may be registered with
345: * several content types. Slave strategies cannot be registered for the
346: * default content type of documents.
347: *
348: * @param strategy The slave formatting strategy
349: * @param type The content type to register this strategy with,
350: * must implement {@link IFormattingStrategyExtension}
351: */
352: public final void setSlaveStrategy(
353: final IFormattingStrategy strategy, final String type) {
354: Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
355: if (!fType.equals(type))
356: fSlaves.put(type, strategy);
357: }
358: }
|