Mercurial > projects > dwt-addons
diff dwtx/jface/text/formatter/ContentFormatter.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/formatter/ContentFormatter.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,801 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ + +module dwtx.jface.text.formatter.ContentFormatter; + +import dwt.dwthelper.utils; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import dwtx.core.runtime.Assert; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.BadPositionCategoryException; +import dwtx.jface.text.DefaultPositionUpdater; +import dwtx.jface.text.DocumentEvent; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentExtension3; +import dwtx.jface.text.IPositionUpdater; +import dwtx.jface.text.IRegion; +import dwtx.jface.text.ITypedRegion; +import dwtx.jface.text.Position; +import dwtx.jface.text.TextUtilities; +import dwtx.jface.text.TypedPosition; + + +/** + * Standard implementation of <code>IContentFormatter</code>. + * The formatter supports two operation modes: partition aware and + * partition unaware. <p> + * In the partition aware mode, the formatter determines the + * partitioning of the document region to be formatted. For each + * partition it determines all document positions which are affected + * when text changes are applied to the partition. Those which overlap + * with the partition are remembered as character positions. These + * character positions are passed over to the formatting strategy + * registered for the partition's content type. The formatting strategy + * returns a string containing the formatted document partition as well + * as the adapted character positions. The formatted partition replaces + * the old content of the partition. The remembered document positions + * are updated with the adapted character positions. In addition, all + * other document positions are accordingly adapted to the formatting + * changes.<p> + * In the partition unaware mode, the document's partitioning is ignored + * and the document is considered consisting of only one partition of + * the content type <code>IDocument.DEFAULT_CONTENT_TYPE</code>. The + * formatting process is similar to the partition aware mode, with the + * exception of having only one partition.<p> + * Usually, clients instantiate this class and configure it before using it. + * + * @see IContentFormatter + * @see IDocument + * @see ITypedRegion + * @see Position + */ +public class ContentFormatter : IContentFormatter { + + /** + * Defines a reference to either the offset or the end offset of + * a particular position. + */ + static class PositionReference : Comparable { + + /** The referenced position */ + protected Position fPosition; + /** The reference to either the offset or the end offset */ + protected bool fRefersToOffset; + /** The original category of the referenced position */ + protected String fCategory; + + /** + * Creates a new position reference. + * + * @param position the position to be referenced + * @param refersToOffset <code>true</code> if position offset should be referenced + * @param category the category the given position belongs to + */ + protected PositionReference(Position position, bool refersToOffset, String category) { + fPosition= position; + fRefersToOffset= refersToOffset; + fCategory= category; + } + + /** + * Returns the offset of the referenced position. + * + * @return the offset of the referenced position + */ + protected int getOffset() { + return fPosition.getOffset(); + } + + /** + * Manipulates the offset of the referenced position. + * + * @param offset the new offset of the referenced position + */ + protected void setOffset(int offset) { + fPosition.setOffset(offset); + } + + /** + * Returns the length of the referenced position. + * + * @return the length of the referenced position + */ + protected int getLength() { + return fPosition.getLength(); + } + + /** + * Manipulates the length of the referenced position. + * + * @param length the new length of the referenced position + */ + protected void setLength(int length) { + fPosition.setLength(length); + } + + /** + * Returns whether this reference points to the offset or end offset + * of the references position. + * + * @return <code>true</code> if the offset of the position is referenced, <code>false</code> otherwise + */ + protected bool refersToOffset() { + return fRefersToOffset; + } + + /** + * Returns the category of the referenced position. + * + * @return the category of the referenced position + */ + protected String getCategory() { + return fCategory; + } + + /** + * Returns the referenced position. + * + * @return the referenced position + */ + protected Position getPosition() { + return fPosition; + } + + /** + * Returns the referenced character position + * + * @return the referenced character position + */ + protected int getCharacterPosition() { + if (fRefersToOffset) + return getOffset(); + return getOffset() + getLength(); + } + + /* + * @see Comparable#compareTo(Object) + */ + public int compareTo(Object obj) { + + if (obj instanceof PositionReference) { + PositionReference r= (PositionReference) obj; + return getCharacterPosition() - r.getCharacterPosition(); + } + + throw new ClassCastException(); + } + } + + /** + * The position updater used to update the remembered partitions. + * + * @see IPositionUpdater + * @see DefaultPositionUpdater + */ + class NonDeletingPositionUpdater : DefaultPositionUpdater { + + /** + * Creates a new updater for the given category. + * + * @param category the category + */ + protected NonDeletingPositionUpdater(String category) { + super(category); + } + + /* + * @see DefaultPositionUpdater#notDeleted() + */ + protected bool notDeleted() { + return true; + } + } + + /** + * The position updater which runs as first updater on the document's positions. + * Used to remove all affected positions from their categories to avoid them + * from being regularly updated. + * + * @see IPositionUpdater + */ + class RemoveAffectedPositions : IPositionUpdater { + /* + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + removeAffectedPositions(event.getDocument()); + } + } + + /** + * The position updater which runs as last updater on the document's positions. + * Used to update all affected positions and adding them back to their + * original categories. + * + * @see IPositionUpdater + */ + class UpdateAffectedPositions : IPositionUpdater { + + /** The affected positions */ + private int[] fPositions; + /** The offset */ + private int fOffset; + + /** + * Creates a new updater. + * + * @param positions the affected positions + * @param offset the offset + */ + public UpdateAffectedPositions(int[] positions, int offset) { + fPositions= positions; + fOffset= offset; + } + + /* + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + updateAffectedPositions(event.getDocument(), fPositions, fOffset); + } + } + + + /** Internal position category used for the formatter partitioning */ + private final static String PARTITIONING= "__formatter_partitioning"; //$NON-NLS-1$ + + /** The map of <code>IFormattingStrategy</code> objects */ + private Map fStrategies; + /** The indicator of whether the formatter operates in partition aware mode or not */ + private bool fIsPartitionAware= true; + + /** The partition information managing document position categories */ + private String[] fPartitionManagingCategories; + /** The list of references to offset and end offset of all overlapping positions */ + private List fOverlappingPositionReferences; + /** Position updater used for partitioning positions */ + private IPositionUpdater fPartitioningUpdater; + /** + * The document partitioning used by this formatter. + * @since 3.0 + */ + private String fPartitioning; + /** + * The document this formatter works on. + * @since 3.0 + */ + private IDocument fDocument; + /** + * The external partition managing categories. + * @since 3.0 + */ + private String[] fExternalPartitonManagingCategories; + /** + * Indicates whether <code>fPartitionManagingCategories</code> must be computed. + * @since 3.0 + */ + private bool fNeedsComputation= true; + + + /** + * Creates a new content formatter. The content formatter operates by default + * in the partition-aware mode. There are no preconfigured formatting strategies. + * Will use the default document partitioning if not further configured. + */ + public ContentFormatter() { + fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; + } + + /** + * Registers a strategy for a particular content type. If there is already a strategy + * registered for this type, the new strategy is registered instead of the old one. + * If the given content type is <code>null</code> the given strategy is registered for + * all content types as is called only once per formatting session. + * + * @param strategy the formatting strategy to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register + */ + public void setFormattingStrategy(IFormattingStrategy strategy, String contentType) { + + Assert.isNotNull(contentType); + + if (fStrategies is null) + fStrategies= new HashMap(); + + if (strategy is null) + fStrategies.remove(contentType); + else + fStrategies.put(contentType, strategy); + } + + /** + * Informs this content formatter about the names of those position categories + * which are used to manage the document's partitioning information and thus should + * be ignored when this formatter updates positions. + * + * @param categories the categories to be ignored + * @deprecated incompatible with an open set of document partitionings. The provided information is only used + * if this formatter can not compute the partition managing position categories. + */ + public void setPartitionManagingPositionCategories(String[] categories) { + fExternalPartitonManagingCategories= TextUtilities.copy(categories); + } + + /** + * Sets the document partitioning to be used by this formatter. + * + * @param partitioning the document partitioning + * @since 3.0 + */ + public void setDocumentPartitioning(String partitioning) { + fPartitioning= partitioning; + } + + /** + * Sets the formatter's operation mode. + * + * @param enable indicates whether the formatting process should be partition ware + */ + public void enablePartitionAwareFormatting(bool enable) { + fIsPartitionAware= enable; + } + + /* + * @see IContentFormatter#getFormattingStrategy(String) + */ + public IFormattingStrategy getFormattingStrategy(String contentType) { + + Assert.isNotNull(contentType); + + if (fStrategies is null) + return null; + + return (IFormattingStrategy) fStrategies.get(contentType); + } + + /* + * @see IContentFormatter#format(IDocument, IRegion) + */ + public void format(IDocument document, IRegion region) { + fNeedsComputation= true; + fDocument= document; + try { + + if (fIsPartitionAware) + formatPartitions(region); + else + formatRegion(region); + + } finally { + fNeedsComputation= true; + fDocument= null; + } + } + + /** + * Determines the partitioning of the given region of the document. + * Informs the formatting strategies of each partition about the start, + * the process, and the termination of the formatting session. + * + * @param region the document region to be formatted + * @since 3.0 + */ + private void formatPartitions(IRegion region) { + + addPartitioningUpdater(); + + try { + + TypedPosition[] ranges= getPartitioning(region); + if (ranges !is null) { + start(ranges, getIndentation(region.getOffset())); + format(ranges); + stop(ranges); + } + + } catch (BadLocationException x) { + } + + removePartitioningUpdater(); + } + + /** + * Formats the given region with the strategy registered for the default + * content type. The strategy is informed about the start, the process, and + * the termination of the formatting session. + * + * @param region the region to be formatted + * @since 3.0 + */ + private void formatRegion(IRegion region) { + + IFormattingStrategy strategy= getFormattingStrategy(IDocument.DEFAULT_CONTENT_TYPE); + if (strategy !is null) { + strategy.formatterStarts(getIndentation(region.getOffset())); + format(strategy, new TypedPosition(region.getOffset(), region.getLength(), IDocument.DEFAULT_CONTENT_TYPE)); + strategy.formatterStops(); + } + } + + /** + * Returns the partitioning of the given region of the document to be formatted. + * As one partition after the other will be formatted and formatting will + * probably change the length of the formatted partition, it must be kept + * track of the modifications in order to submit the correct partition to all + * formatting strategies. For this, all partitions are remembered as positions + * in a dedicated position category. (As formatting strategies might rely on each + * other, calling them in reversed order is not an option.) + * + * @param region the region for which the partitioning must be determined + * @return the partitioning of the specified region + * @exception BadLocationException of region is invalid in the document + * @since 3.0 + */ + private TypedPosition[] getPartitioning(IRegion region) throws BadLocationException { + + ITypedRegion[] regions= TextUtilities.computePartitioning(fDocument, fPartitioning, region.getOffset(), region.getLength(), false); + TypedPosition[] positions= new TypedPosition[regions.length]; + + for (int i= 0; i < regions.length; i++) { + positions[i]= new TypedPosition(regions[i]); + try { + fDocument.addPosition(PARTITIONING, positions[i]); + } catch (BadPositionCategoryException x) { + // should not happen + } + } + + return positions; + } + + /** + * Fires <code>formatterStarts</code> to all formatter strategies + * which will be involved in the forthcoming formatting process. + * + * @param regions the partitioning of the document to be formatted + * @param indentation the initial indentation + */ + private void start(TypedPosition[] regions, String indentation) { + for (int i= 0; i < regions.length; i++) { + IFormattingStrategy s= getFormattingStrategy(regions[i].getType()); + if (s !is null) + s.formatterStarts(indentation); + } + } + + /** + * Formats one partition after the other using the formatter strategy registered for + * the partition's content type. + * + * @param ranges the partitioning of the document region to be formatted + * @since 3.0 + */ + private void format(TypedPosition[] ranges) { + for (int i= 0; i < ranges.length; i++) { + IFormattingStrategy s= getFormattingStrategy(ranges[i].getType()); + if (s !is null) { + format(s, ranges[i]); + } + } + } + + /** + * Formats the given region of the document using the specified formatting + * strategy. In order to maintain positions correctly, first all affected + * positions determined, after all document listeners have been informed about + * the coming change, the affected positions are removed to avoid that they + * are regularly updated. After all position updaters have run, the affected + * positions are updated with the formatter's information and added back to + * their categories, right before the first document listener is informed about + * that a change happened. + * + * @param strategy the strategy to be used + * @param region the region to be formatted + * @since 3.0 + */ + private void format(IFormattingStrategy strategy, TypedPosition region) { + try { + + final int offset= region.getOffset(); + int length= region.getLength(); + + String content= fDocument.get(offset, length); + final int[] positions= getAffectedPositions(offset, length); + String formatted= strategy.format(content, isLineStart(offset), getIndentation(offset), positions); + + if (formatted !is null && !formatted.equals(content)) { + + IPositionUpdater first= new RemoveAffectedPositions(); + fDocument.insertPositionUpdater(first, 0); + IPositionUpdater last= new UpdateAffectedPositions(positions, offset); + fDocument.addPositionUpdater(last); + + fDocument.replace(offset, length, formatted); + + fDocument.removePositionUpdater(first); + fDocument.removePositionUpdater(last); + } + + } catch (BadLocationException x) { + // should not happen + } + } + + /** + * Fires <code>formatterStops</code> to all formatter strategies which were + * involved in the formatting process which is about to terminate. + * + * @param regions the partitioning of the document which has been formatted + */ + private void stop(TypedPosition[] regions) { + for (int i= 0; i < regions.length; i++) { + IFormattingStrategy s= getFormattingStrategy(regions[i].getType()); + if (s !is null) + s.formatterStops(); + } + } + + /** + * Installs those updaters which the formatter needs to keep track of the partitions. + * @since 3.0 + */ + private void addPartitioningUpdater() { + fPartitioningUpdater= new NonDeletingPositionUpdater(PARTITIONING); + fDocument.addPositionCategory(PARTITIONING); + fDocument.addPositionUpdater(fPartitioningUpdater); + } + + /** + * Removes the formatter's internal position updater and category. + * + * @since 3.0 + */ + private void removePartitioningUpdater() { + + try { + + fDocument.removePositionUpdater(fPartitioningUpdater); + fDocument.removePositionCategory(PARTITIONING); + fPartitioningUpdater= null; + + } catch (BadPositionCategoryException x) { + // should not happen + } + } + + /** + * Returns the partition managing position categories for the formatted document. + * + * @return the position managing position categories + * @since 3.0 + */ + private String[] getPartitionManagingCategories() { + if (fNeedsComputation) { + fNeedsComputation= false; + fPartitionManagingCategories= TextUtilities.computePartitionManagingCategories(fDocument); + if (fPartitionManagingCategories is null) + fPartitionManagingCategories= fExternalPartitonManagingCategories; + } + return fPartitionManagingCategories; + } + + /** + * Determines whether the given document position category should be ignored + * by this formatter's position updating. + * + * @param category the category to check + * @return <code>true</code> if the category should be ignored, <code>false</code> otherwise + */ + private bool ignoreCategory(String category) { + + if (PARTITIONING.equals(category)) + return true; + + String[] categories= getPartitionManagingCategories(); + if (categories !is null) { + for (int i= 0; i < categories.length; i++) { + if (categories[i].equals(category)) + return true; + } + } + + return false; + } + + /** + * Determines all embracing, overlapping, and follow up positions + * for the given region of the document. + * + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + * @since 3.0 + */ + private void determinePositionsToUpdate(int offset, int length) { + + String[] categories= fDocument.getPositionCategories(); + if (categories !is null) { + for (int i= 0; i < categories.length; i++) { + + if (ignoreCategory(categories[i])) + continue; + + try { + + Position[] positions= fDocument.getPositions(categories[i]); + + for (int j= 0; j < positions.length; j++) { + + Position p= positions[j]; + if (p.overlapsWith(offset, length)) { + + if (offset < p.getOffset()) + fOverlappingPositionReferences.add(new PositionReference(p, true, categories[i])); + + if (p.getOffset() + p.getLength() < offset + length) + fOverlappingPositionReferences.add(new PositionReference(p, false, categories[i])); + } + } + + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + } + + /** + * Returns all offset and the end offset of all positions overlapping with the + * specified document range. + * + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + * @return all character positions of the interleaving positions + * @since 3.0 + */ + private int[] getAffectedPositions(int offset, int length) { + + fOverlappingPositionReferences= new ArrayList(); + + determinePositionsToUpdate(offset, length); + + Collections.sort(fOverlappingPositionReferences); + + int[] positions= new int[fOverlappingPositionReferences.size()]; + for (int i= 0; i < positions.length; i++) { + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + positions[i]= r.getCharacterPosition() - offset; + } + + return positions; + } + + /** + * Removes the affected positions from their categories to avoid + * that they are invalidly updated. + * + * @param document the document + */ + private void removeAffectedPositions(IDocument document) { + int size= fOverlappingPositionReferences.size(); + for (int i= 0; i < size; i++) { + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + try { + document.removePosition(r.getCategory(), r.getPosition()); + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + + /** + * Updates all the overlapping positions. Note, all other positions are + * automatically updated by their document position updaters. + * + * @param document the document to has been formatted + * @param positions the adapted character positions to be used to update the document positions + * @param offset the offset of the document region that has been formatted + */ + protected void updateAffectedPositions(IDocument document, int[] positions, int offset) { + + if (document !is fDocument) + return; + + if (positions.length is 0) + return; + + for (int i= 0; i < positions.length; i++) { + + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + + if (r.refersToOffset()) + r.setOffset(offset + positions[i]); + else + r.setLength((offset + positions[i]) - r.getOffset()); + + Position p= r.getPosition(); + String category= r.getCategory(); + if (!document.containsPosition(category, p.offset, p.length)) { + try { + if (positionAboutToBeAdded(document, category, p)) + document.addPosition(r.getCategory(), p); + } catch (BadPositionCategoryException x) { + // can not happen + } catch (BadLocationException x) { + // should not happen + } + } + + } + + fOverlappingPositionReferences= null; + } + + /** + * The given position is about to be added to the given position category of the given document. <p> + * This default implementation return <code>true</code>. + * + * @param document the document + * @param category the position category + * @param position the position that will be added + * @return <code>true</code> if the position can be added, <code>false</code> if it should be ignored + */ + protected bool positionAboutToBeAdded(IDocument document, String category, Position position) { + return true; + } + + /** + * Returns the indentation of the line of the given offset. + * + * @param offset the offset + * @return the indentation of the line of the offset + * @since 3.0 + */ + private String getIndentation(int offset) { + + try { + int start= fDocument.getLineOfOffset(offset); + start= fDocument.getLineOffset(start); + + int end= start; + char c= fDocument.getChar(end); + while ('\t' is c || ' ' is c) + c= fDocument.getChar(++end); + + return fDocument.get(start, end - start); + } catch (BadLocationException x) { + } + + return ""; //$NON-NLS-1$ + } + + /** + * Determines whether the offset is the beginning of a line in the given document. + * + * @param offset the offset + * @return <code>true</code> if offset is the beginning of a line + * @exception BadLocationException if offset is invalid in document + * @since 3.0 + */ + private bool isLineStart(int offset) throws BadLocationException { + int start= fDocument.getLineOfOffset(offset); + start= fDocument.getLineOffset(start); + return (start is offset); + } +}