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);
+    }
+}