blob: 7abacbb4a89db212c430722412fca301b202265b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2014 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
* Sergey Prigogin (Google) - bug 441448
*******************************************************************************/
package org.eclipse.jface.text.formatter;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.TypedPosition;
/**
* Content formatter for edit-based formatting strategies.
* <p>
* Two kinds of formatting strategies can be registered with this formatter:
* <ul>
* <li>one master formatting strategy for the default content type</li>
* <li>one formatting strategy for each non-default content type</li>
* </ul>
* The master formatting strategy always formats the whole region to be
* formatted in the first pass. In a second pass, all partitions of the region
* to be formatted that are not of master content type are formatted using the
* slave formatting strategy registered for the underlying content type. All
* formatting strategies must implement {@link IFormattingStrategyExtension}.
* <p>
* Regions to be formatted with the master formatting strategy always have
* an offset aligned to the line start. Regions to be formatted with slave formatting
* strategies are aligned on partition boundaries.
*
* @see IFormattingStrategyExtension
* @since 3.0
*/
public class MultiPassContentFormatter implements IContentFormatter, IContentFormatterExtension {
/**
* Position updater that shifts otherwise deleted positions to the next
* non-whitespace character. The length of the positions are truncated to
* one if the position was shifted.
*/
protected class NonDeletingPositionUpdater extends DefaultPositionUpdater {
/**
* Creates a new non-deleting position updater.
*
* @param category The position category to update its positions
*/
public NonDeletingPositionUpdater(final String category) {
super(category);
}
@Override
protected final boolean notDeleted() {
if (fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) {
int offset= fOffset + fLength;
if (offset < fDocument.getLength()) {
try {
boolean moved= false;
char character= fDocument.getChar(offset);
while (offset < fDocument.getLength() && Character.isWhitespace(character)) {
moved= true;
character= fDocument.getChar(offset++);
}
if (moved)
offset--;
} catch (BadLocationException exception) {
// Can not happen
}
fPosition.offset= offset;
fPosition.length= 0;
}
}
return true;
}
}
/** The master formatting strategy */
private IFormattingStrategyExtension fMaster= null;
/** The partitioning of this content formatter */
private final String fPartitioning;
/** The slave formatting strategies */
private final Map<String, IFormattingStrategy> fSlaves= new HashMap<>();
/** The default content type */
private final String fType;
/**
* Creates a new content formatter.
*
* @param partitioning the document partitioning for this formatter
* @param type the default content type
*/
public MultiPassContentFormatter(final String partitioning, final String type) {
fPartitioning= partitioning;
fType= type;
}
@Override
public final void format(final IDocument medium, final IFormattingContext context) {
context.setProperty(FormattingContextProperties.CONTEXT_MEDIUM, medium);
final Boolean document= (Boolean)context.getProperty(FormattingContextProperties.CONTEXT_DOCUMENT);
if (document == null || !document.booleanValue()) {
final IRegion region= (IRegion)context.getProperty(FormattingContextProperties.CONTEXT_REGION);
if (region != null) {
try {
formatMaster(context, medium, region.getOffset(), region.getLength());
} finally {
formatSlaves(context, medium, region.getOffset(), region.getLength());
}
}
} else {
try {
formatMaster(context, medium, 0, medium.getLength());
} finally {
formatSlaves(context, medium, 0, medium.getLength());
}
}
}
@Override
public final void format(final IDocument medium, final IRegion region) {
final FormattingContext context= new FormattingContext();
context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.FALSE);
context.setProperty(FormattingContextProperties.CONTEXT_REGION, region);
format(medium, context);
}
/**
* Formats the document specified in the formatting context with the master
* formatting strategy.
* <p>
* The master formatting strategy covers all regions of the document. The
* offset of the region to be formatted is aligned on line start boundaries,
* whereas the end index of the region remains the same. For this formatting
* type the document partitioning is not taken into account.
*
* @param context The formatting context to use
* @param document The document to operate on
* @param offset The offset of the region to format
* @param length The length of the region to format
*/
protected void formatMaster(final IFormattingContext context, final IDocument document, int offset, int length) {
try {
if (length != 0) {
// Extend the selection to the beginning of line if it is not empty.
// An empty selection must remain empty since it may be treated in
// a special way by the formatter.
final int delta= offset - document.getLineInformationOfOffset(offset).getOffset();
offset -= delta;
length += delta;
}
} catch (BadLocationException exception) {
// Do nothing
}
if (fMaster != null) {
context.setProperty(FormattingContextProperties.CONTEXT_PARTITION, new TypedPosition(offset, length, fType));
fMaster.formatterStarts(context);
fMaster.format();
fMaster.formatterStops();
}
}
/**
* Formats the document specified in the formatting context with the
* formatting strategy registered for the content type.
* <p>
* For this formatting type only slave strategies are used. The region to be
* formatted is aligned on partition boundaries of the underlying content
* type. The exact formatting strategy is determined by the underlying
* content type of the document partitioning.
*
* @param context The formatting context to use
* @param document The document to operate on
* @param offset The offset of the region to format
* @param length The length of the region to format
* @param type The content type of the region to format
*/
protected void formatSlave(final IFormattingContext context, final IDocument document, final int offset, final int length, final String type) {
final IFormattingStrategyExtension strategy= (IFormattingStrategyExtension)fSlaves.get(type);
if (strategy != null) {
context.setProperty(FormattingContextProperties.CONTEXT_PARTITION, new TypedPosition(offset, length, type));
strategy.formatterStarts(context);
strategy.format();
strategy.formatterStops();
}
}
/**
* Formats the document specified in the formatting context with the slave
* formatting strategies.
* <p>
* For each content type of the region to be formatted in the document
* partitioning, the registered slave formatting strategy is used to format
* that particular region. The region to be formatted is aligned on
* partition boundaries of the underlying content type. If the content type
* is the document's default content type, nothing happens.
*
* @param context The formatting context to use
* @param document The document to operate on
* @param offset The offset of the region to format
* @param length The length of the region to format
*/
protected void formatSlaves(final IFormattingContext context, final IDocument document, final int offset, final int length) {
Map<String, IDocumentPartitioner> partitioners= new HashMap<>(0);
try {
final ITypedRegion[] partitions= TextUtilities.computePartitioning(document, fPartitioning, offset, length, false);
if (!fType.equals(partitions[0].getType()))
partitions[0]= TextUtilities.getPartition(document, fPartitioning, partitions[0].getOffset(), false);
if (partitions.length > 1) {
if (!fType.equals(partitions[partitions.length - 1].getType()))
partitions[partitions.length - 1]= TextUtilities.getPartition(document, fPartitioning, partitions[partitions.length - 1].getOffset(), false);
}
String type= null;
ITypedRegion partition= null;
partitioners= TextUtilities.removeDocumentPartitioners(document);
for (int index= partitions.length - 1; index >= 0; index--) {
partition= partitions[index];
type= partition.getType();
if (!fType.equals(type))
formatSlave(context, document, partition.getOffset(), partition.getLength(), type);
}
} catch (BadLocationException exception) {
// Should not happen
} finally {
TextUtilities.addDocumentPartitioners(document, partitioners);
}
}
@Override
public final IFormattingStrategy getFormattingStrategy(final String type) {
return null;
}
/**
* Registers a master formatting strategy.
* <p>
* The strategy may already be registered with a certain content type as
* slave strategy. The master strategy is registered for the default content
* type of documents. If a master strategy has already been registered, it
* is overridden by the new one.
*
* @param strategy The master formatting strategy, must implement
* {@link IFormattingStrategyExtension}
*/
public final void setMasterStrategy(final IFormattingStrategy strategy) {
Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
fMaster= (IFormattingStrategyExtension) strategy;
}
/**
* Registers a slave formatting strategy for a certain content type.
* <p>
* The strategy may already be registered as master strategy. An
* already registered slave strategy for the specified content type
* will be replaced. However, the same strategy may be registered with
* several content types. Slave strategies cannot be registered for the
* default content type of documents.
*
* @param strategy The slave formatting strategy
* @param type The content type to register this strategy with,
* must implement {@link IFormattingStrategyExtension}
*/
public final void setSlaveStrategy(final IFormattingStrategy strategy, final String type) {
Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
if (!fType.equals(type))
fSlaves.put(type, strategy);
}
}