Package com.dubture.twig.core.format

Source Code of com.dubture.twig.core.format.TwigFormatter

/*******************************************************************************
* This file is part of the Twig eclipse plugin.
*
* (c) Robert Gruendler <r.gruendler@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
******************************************************************************/
package com.dubture.twig.core.format;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.php.internal.core.format.CommentIndentationStrategy;
import org.eclipse.php.internal.core.format.IIndentationStrategy;
import org.eclipse.php.internal.core.format.PhpFormatConstraints;
import org.eclipse.wst.sse.core.internal.format.IStructuredFormatContraints;
import org.eclipse.wst.sse.core.internal.format.IStructuredFormatPreferences;
import org.eclipse.wst.sse.core.internal.format.IStructuredFormatter;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.text.rules.SimpleStructuredRegion;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.w3c.dom.Node;

import com.dubture.twig.core.documentModel.dom.AttrImplForTwig;
import com.dubture.twig.core.documentModel.dom.ElementImplForTwig;
import com.dubture.twig.core.documentModel.parser.partitioner.TwigPartitionTypes;
import com.dubture.twig.core.documentModel.parser.regions.ITwigScriptRegion;
import com.dubture.twig.core.documentModel.parser.regions.TwigRegionTypes;
import com.dubture.twig.core.documentModel.parser.regions.TwigScriptRegion;
import com.dubture.twig.core.log.Logger;

@SuppressWarnings("restriction")
public class TwigFormatter implements IStructuredFormatter
{

    private final IIndentationStrategy defaultIndentationStrategy = new DefaultIndentationStrategy();
    private final IIndentationStrategy curlyCloseIndentationStrategy = new CurlyCloseIndentationStrategy();
    private final IIndentationStrategy commentIndentationStrategy = new CommentIndentationStrategy();
    private final IIndentationStrategy phpCloseTagIndentationStrategy = new TwigCloseTagIndentationStrategy();

    protected PhpFormatConstraints fFormatContraints = null;
    protected IStructuredFormatPreferences fFormatPreferences = null;
    protected IProgressMonitor fProgressMonitor = null;
    private final int length;
    private final int start;

    private static final byte CHAR_TAB = '\t';
    private static final byte CHAR_SPACE = ' ';

    public TwigFormatter(int start, int length)
    {
        this.start = start;
        this.length = length;
    }

    public void format(Node node)
    {
        format(node, getFormatContraints());
    }

    public void format(Node node, IStructuredFormatContraints formatContraints)
    {
        if (node instanceof IDOMNode) {
            formatNode((IDOMNode) node, formatContraints);
        }

    }

    /**
     * Recursivly call the format node on every node in the model
     *
     * @param node
     *            - to fotmat
     * @param formatContraints
     */
    private void formatNode(IDOMNode node,
            IStructuredFormatContraints formatContraints)
    {

        // if it is php node - format
        if (node instanceof ElementImplForTwig
                && ((ElementImplForTwig) node).isTwigTag()) {
            IStructuredDocumentRegion sdRegionStart = node
                    .getStartStructuredDocumentRegion();
            // IStructuredDocumentRegion sdRegionEnd =
            // node.getLastStructuredDocumentRegion();
            // sdRegionEnd = sdRegionEnd == null ? sdRegionStart : sdRegionEnd;
            format(sdRegionStart);
        }

        if (node instanceof AttrImplForTwig) {
            // calculate lines
            IStructuredDocument document = node.getStructuredDocument();
            int lineIndex = document.getLineOfOffset(node.getStartOffset());
            int endLineIndex = document.getLineOfOffset(node.getEndOffset());

            // format each line
            for (; lineIndex <= endLineIndex; lineIndex++) {
                formatLine(document, lineIndex);
            }
        }

        if (node.hasChildNodes()) { // container
            IDOMNode child = (IDOMNode) node.getFirstChild();
            while (child != null) {
                formatNode(child, formatContraints);
                child = (IDOMNode) child.getNextSibling();
            }
        }
    }

    public IStructuredFormatContraints getFormatContraints()
    {
        if (fFormatContraints == null) {
            fFormatContraints = new PhpFormatConstraints();
        }
        return fFormatContraints;
    }

    public IStructuredFormatPreferences getFormatPreferences()
    {
        return fFormatPreferences;
    }

    public void setFormatPreferences(
            IStructuredFormatPreferences formatPreferences)
    {
        this.fFormatPreferences = formatPreferences;
    }

    public void setProgressMonitor(IProgressMonitor monitor)
    {
        this.fProgressMonitor = monitor;
    }

    protected final int getStart()
    {
        return start;
    }

    protected final int getLength()
    {
        return length;
    }

    public void format(IStructuredDocumentRegion sdRegion)
    {
        assert sdRegion != null;

        // resolce formatter range
        int regionStart = sdRegion.getStartOffset();
        int regionEnd = sdRegion.getEnd();

        int formatRequestStart = getStart();
        int formatRequestEnd = formatRequestStart + getLength();

        int startFormat = Math.max(formatRequestStart, regionStart);
        int endFormat = Math.min(formatRequestEnd, regionEnd);

        // calculate lines
        IStructuredDocument document = sdRegion.getParentDocument();
        int lineIndex = document.getLineOfOffset(startFormat);
        int endLineIndex = document.getLineOfOffset(endFormat);
        ITextRegionList textRegions = sdRegion.getRegions();
        String newline = document.getLineDelimiter();
        for (int i = 0; i < textRegions.size(); i++) {
            ITextRegion textRegion = textRegions.get(i);
            if (textRegion instanceof TwigScriptRegion) {
                int startOffset = sdRegion.getStartOffset(textRegion);
                TwigScriptRegion scriptRegion = (TwigScriptRegion) textRegion;
                ITextRegion[] twigTokens;
                try {
                    twigTokens = scriptRegion.getTwigTokens(0,
                            textRegion.getLength());
                    for (int j = twigTokens.length - 1; j >= 0; j--) {
                        ITextRegion phpToken = twigTokens[j];
                        int start = startOffset + phpToken.getStart();
                        int end = start + phpToken.getLength();
                        if (/* endFormat >= end || */startFormat <= start
                                && endFormat >= end) {
                            if (phpToken.getType().equals(
                                    TwigRegionTypes.PHP_CURLY_OPEN)) {
                                if (j < twigTokens.length - 1 && j > 0) {
                                    if (twigTokens[j - 1].getType().equals(
                                            TwigRegionTypes.TWIG_DELIMITER)
                                            && document
                                                    .getLineOfOffset(startOffset
                                                            + phpToken
                                                                    .getStart()) == document
                                                    .getLineOfOffset(startOffset
                                                            + twigTokens[j + 1]
                                                                    .getStart())) {
                                        document.replace(
                                                startOffset + phpToken.getEnd(),
                                                0, newline);
                                        endLineIndex++;
                                    }
                                }
                            } else if (phpToken.getType().equals(
                                    TwigRegionTypes.PHP_CURLY_CLOSE)) {
                                if (j > 0
                                        && (twigTokens[j - 1].getType().equals(
                                                TwigRegionTypes.PHP_SEMICOLON)
                                                || twigTokens[j - 1]
                                                        .getType()
                                                        .equals(TwigRegionTypes.PHP_CURLY_CLOSE) || twigTokens[j - 1]
                                                .getType()
                                                .equals(TwigRegionTypes.TWIG_COMMENT_CLOSE))) {
                                    if (document.getLineOfOffset(startOffset
                                            + phpToken.getStart()) == document
                                            .getLineOfOffset(startOffset
                                                    + twigTokens[j - 1]
                                                            .getStart())) {
                                        document.replace(startOffset
                                                + twigTokens[j - 1].getEnd(),
                                                0, newline);
                                        endLineIndex++;
                                    }
                                }
                            }
                        }

                    }
                } catch (BadLocationException e) {
                }
            }
        }
        sdRegion.getRegionAtCharacterOffset(startFormat);
        // TODO get token of each line then insert line seporator after { and
        // after } if there is no line seporator
        // format each line
        for (; lineIndex <= endLineIndex; lineIndex++) {
            formatLine(document, lineIndex);
        }

    }

    private final StringBuffer resultBuffer = new StringBuffer();

    /**
     * formats a PHP line according to the strategies and formatting conventions
     *
     * @param document
     * @param lineNumber
     *            TODO: we should invoke document.replace() one and not twice!
     */
    private void formatLine(IStructuredDocument document, int lineNumber)
    {
        resultBuffer.setLength(0);

        try {

            // get original line information
            final IRegion originalLineInfo = document
                    .getLineInformation(lineNumber);
            final int orginalLineStart = originalLineInfo.getOffset();
            final int originalLineLength = originalLineInfo.getLength();

            // fast resolving of empty line
            if (originalLineLength == 0)
                return;

            // get formatted line information
            final String lineText = document.get(orginalLineStart,
                    originalLineLength);
            final IRegion formattedLineInformation = getFormattedLineInformation(
                    originalLineInfo, lineText);

            if (!shouldReformat(document, formattedLineInformation)) {
                return;
            }

            // remove ending spaces.
            final int formattedLineStart = formattedLineInformation.getOffset();
            final int formattedTextEnd = formattedLineStart
                    + formattedLineInformation.getLength();
            final int originalTextEnd = orginalLineStart + originalLineLength;
            if (formattedTextEnd != originalTextEnd) {
                document.replace(formattedTextEnd, originalTextEnd
                        - formattedTextEnd, ""); //$NON-NLS-1$
                // in case there is no text in the line just quit (since the
                // formatted of empty line is empty line)
                if (formattedLineStart == formattedTextEnd) {
                    return;
                }
            }

            // get regions
            final int startingWhiteSpaces = formattedLineStart
                    - orginalLineStart;
            final IIndentationStrategy insertionStrategy;
            final IStructuredDocumentRegion sdRegion = document
                    .getRegionAtCharacterOffset(formattedLineStart);
            ITextRegion firstTokenInLine = sdRegion
                    .getRegionAtCharacterOffset(formattedLineStart);
            int regionStart = sdRegion.getStartOffset(firstTokenInLine);
            if (firstTokenInLine instanceof ITextRegionContainer) {
                ITextRegionContainer container = (ITextRegionContainer) firstTokenInLine;
                firstTokenInLine = container
                        .getRegionAtCharacterOffset(formattedLineStart);
                regionStart += firstTokenInLine.getStart();
            }

            if (firstTokenInLine instanceof ITwigScriptRegion) {
                ITwigScriptRegion scriptRegion = (ITwigScriptRegion) firstTokenInLine;
                firstTokenInLine = scriptRegion.getTwigToken(formattedLineStart
                        - regionStart);
                if (firstTokenInLine != null
                        && firstTokenInLine.getStart()
                                + sdRegion.getStartOffset() < orginalLineStart
                        && firstTokenInLine.getType() == TwigRegionTypes.WHITESPACE) {
                    firstTokenInLine = scriptRegion
                            .getTwigToken(formattedLineStart - regionStart
                                    + firstTokenInLine.getLength());
                }
            }

            // if the first char is not from this line
            if (firstTokenInLine == null)
                return;

            /*
             * if (firstTokenInLine.getStart() + sdRegion.getStartOffset() <
             * originalLineInfo.getOffset() || firstTokenInLine.getType() ==
             * PHPRegionTypes.WHITESPACE) { //meaning we got previos line last
             * token firstTokenInLine =
             * sdRegion.getRegionAtCharacterOffset(sdRegion.getStartOffset() +
             * firstTokenInLine.getEnd()); }
             */

            // if the next char is not from this line
            // if (firstTokenInLine == null)
            // return;

            String firstTokenType = firstTokenInLine.getType();
            /*
             * if (firstTokenType == TwigRegionTypes.TWIG_CONTENT) {
             * insertionStrategy = caseDefaultIndentationStrategy; } else
             */if (isTwigCommentRegion(firstTokenType)) {
                insertionStrategy = commentIndentationStrategy;
            } else if (firstTokenType == TwigRegionTypes.TWIG_CLOSETAG) {
                insertionStrategy = phpCloseTagIndentationStrategy;
            } else {
                insertionStrategy = getIndentationStrategy(lineText
                        .charAt(startingWhiteSpaces));
            }

            // Fill the buffer with blanks as if we added a "\n" to the end of
            // the prev element.
            // insertionStrategy.placeMatchingBlanks(editor,doc,insertionStrtegyKey,resultBuffer,startOffset-1);
            insertionStrategy.placeMatchingBlanks(document, resultBuffer,
                    lineNumber, document.getLineOffset(lineNumber));

            // replace the starting spaces
            final String newIndentation = resultBuffer.toString();
            final String oldIndentation = lineText.substring(0,
                    startingWhiteSpaces);
            char newChar = '\0';
            if (newIndentation.length() > 0) {
                newChar = newIndentation.charAt(0);
            }
            char oldChar = '\0';
            if (oldIndentation.length() > 0) {
                oldChar = oldIndentation.charAt(0);
            }
            if (newIndentation.length() != oldIndentation.length()
                    || newChar != oldChar) {
                document.replaceText(sdRegion, orginalLineStart,
                        startingWhiteSpaces, newIndentation);
            }

        } catch (BadLocationException e) {
            Logger.logException(e);
        }
    }

    /**
     * @return whether we are inside a php comment
     */
    private boolean isTwigCommentRegion(String tokenType)
    {
        return (tokenType == TwigRegionTypes.TWIG_COMMENT
                || tokenType == TwigRegionTypes.TWIG_COMMENT_TEXT || tokenType == TwigRegionTypes.TWIG_COMMENT_CLOSE);
    }

    /**
     * @return the formatted line (without whitespaces) information Note: this
     *         is an O(n) implementation (firstly it looks a bit complicated but
     *         it worth it, the previous version was 3*n on the string's length)
     */
    private IRegion getFormattedLineInformation(IRegion lineInfo,
            String lineText)
    {
        // start checking from left and right to the center
        int leftNonWhitespaceChar = 0;
        int rightNonWhitespaceChar = lineText.length() - 1;
        final char[] chars = lineText.toCharArray();
        boolean keepSearching = true;

        while (keepSearching) {
            final boolean leftIsWhiteSpace = chars[leftNonWhitespaceChar] == CHAR_SPACE
                    || chars[leftNonWhitespaceChar] == CHAR_TAB;
            final boolean rightIsWhiteSpace = chars[rightNonWhitespaceChar] == CHAR_SPACE
                    || chars[rightNonWhitespaceChar] == CHAR_TAB;
            if (leftIsWhiteSpace)
                leftNonWhitespaceChar++;
            if (rightIsWhiteSpace)
                rightNonWhitespaceChar--;
            keepSearching = (leftIsWhiteSpace || rightIsWhiteSpace)
                    && (leftNonWhitespaceChar < rightNonWhitespaceChar);
        }

        // if line is empty then the indexes were switched
        if (leftNonWhitespaceChar > rightNonWhitespaceChar)
            return new SimpleStructuredRegion(lineInfo.getOffset(), 0);

        // if there are no changes - return the original line information, else
        // build a fixed region
        return leftNonWhitespaceChar == 0
                && rightNonWhitespaceChar == lineText.length() - 1
                ? lineInfo
                : new SimpleStructuredRegion(lineInfo.getOffset()
                        + leftNonWhitespaceChar, rightNonWhitespaceChar
                        - leftNonWhitespaceChar + 1);
    }

    private boolean shouldReformat(IStructuredDocument document,
            IRegion lineInfo)
    {
        final String checkedLineBeginState = FormatterUtils.getPartitionType(
                document, lineInfo.getOffset());
        return ((checkedLineBeginState == TwigPartitionTypes.TWIG_DEFAULT) || (checkedLineBeginState == TwigPartitionTypes.TWIG_COMMENT_TEXT))
                || (checkedLineBeginState == TwigPartitionTypes.TWIG_COMMENT_TEXT);
    }

    protected IIndentationStrategy getIndentationStrategy(char c)
    {
        if (c == '}') {
            return curlyCloseIndentationStrategy;
        }
        return getDefaultIndentationStrategy();
    }

    private IIndentationStrategy getDefaultIndentationStrategy()
    {
        return defaultIndentationStrategy;
    }

}
TOP

Related Classes of com.dubture.twig.core.format.TwigFormatter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.