| /******************************************************************************* |
| * 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); |
| } |
| } |