com.aptana.ide.editor.jscomment.formatting.JSCommentAutoIndentStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.ide.editor.jscomment.formatting.JSCommentAutoIndentStrategy.java

Source

/**
 * This file Copyright (c) 2005-2008 Aptana, Inc. This program is
 * dual-licensed under both the Aptana Public License and the GNU General
 * Public license. You may elect to use one or the other of these licenses.
 * 
 * This program is distributed in the hope that it will be useful, but
 * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
 * NONINFRINGEMENT. Redistribution, except as permitted by whichever of
 * the GPL or APL you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or modify this
 * program under the terms of the GNU General Public License,
 * Version 3, as published by the Free Software Foundation.  You should
 * have received a copy of the GNU General Public License, Version 3 along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Aptana provides a special exception to allow redistribution of this file
 * with certain other free and open source software ("FOSS") code and certain additional terms
 * pursuant to Section 7 of the GPL. You may view the exception and these
 * terms on the web at http://www.aptana.com/legal/gpl/.
 * 
 * 2. For the Aptana Public License (APL), this program and the
 * accompanying materials are made available under the terms of the APL
 * v1.0 which accompanies this distribution, and is available at
 * http://www.aptana.com/legal/apl/.
 * 
 * You may view the GPL, Aptana's exception and additional terms, and the
 * APL in the file titled license.html at the root of the corresponding
 * plugin containing this source file.
 * 
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.ide.editor.jscomment.formatting;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;

import com.aptana.ide.editor.js.JSPlugin;
import com.aptana.ide.editor.js.preferences.IPreferenceConstants;
import com.aptana.ide.editor.jscomment.JSCommentFileLanguageService;
import com.aptana.ide.editor.jscomment.parsing.JSCommentMimeType;
import com.aptana.ide.editors.unified.EditorFileContext;
import com.aptana.ide.editors.unified.UnifiedAutoIndentStrategy;
import com.aptana.ide.editors.unified.UnifiedConfiguration;
import com.aptana.ide.lexer.LexemeList;

/**
 * 
 */
public class JSCommentAutoIndentStrategy extends UnifiedAutoIndentStrategy {
    /**
     * linePrefix
     */
    protected String linePrefix = "* "; // " "; // //$NON-NLS-1$

    /**
     * @param context
     * @param configuration
     * @param sourceViewer
     */
    public JSCommentAutoIndentStrategy(EditorFileContext context, SourceViewerConfiguration configuration,
            ISourceViewer sourceViewer) {
        super(context, configuration, sourceViewer);
    }

    /**
     * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument,
     *      org.eclipse.jface.text.DocumentCommand)
     */
    public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
        if (command.text == null || command.length > 0) {
            return;
        }

        String[] lineDelimiters = document.getLegalLineDelimiters();
        int index = TextUtilities.endsWith(lineDelimiters, command.text);
        if (index > -1) {
            // ends with line delimiter
            if (lineDelimiters[index].equals(command.text)) {
                indentAfterNewLine(document, command);
            }
            return;
        }
        // todo: ensure we actually need this here
        else if (command.text.equals("\t")) //$NON-NLS-1$
        {
            if (configuration instanceof UnifiedConfiguration) {
                UnifiedConfiguration uc = (UnifiedConfiguration) configuration;
                if (uc.useSpacesAsTabs()) {
                    command.text = uc.getTabAsSpaces();
                }
            }
        }
    }

    /**
     * indentCloseToken
     * 
     * @param doc
     * @param c
     * @param offset
     * @param lineOffset
     * @param firstNonWS
     * @return boolean
     */
    protected boolean indentCloseToken(IDocument doc, DocumentCommand c, int offset, int lineOffset,
            int firstNonWS) {
        boolean isClose = false;
        if (doc.getLength() < 2 || offset < 2) {
            isClose = true;
        } else {
            try {
                if (doc.getChar(offset - 1) == '/' && doc.getChar(offset - 2) == '*') {
                    isClose = true;
                }
            } catch (BadLocationException e) {
            }
        }

        if (isClose) {
            String append = getIndentationString(doc, lineOffset, firstNonWS);
            // multiline comments indent with "space *" after the first, so trim that if it is there
            if (append.endsWith(" ")) //$NON-NLS-1$
            {
                append = append.substring(0, append.length() - 1);
            }
            c.text += append;
            return true;
        }

        return false;
    }

    /**
     * Copies the indentation of the previous line and adds a star. If the javadoc just started on
     * this line add standard method tags and close the javadoc.
     * 
     * @param d
     *            the document to work on
     * @param c
     *            the command to deal with
     */
    protected void indentAfterNewLine(IDocument d, DocumentCommand c) {

        int offset = c.offset;
        if (offset == -1 || d.getLength() == 0) {
            return;
        }

        try {
            int p = (offset == d.getLength() ? offset - 1 : offset);
            IRegion line = d.getLineInformationOfOffset(p);

            int lineOffset = line.getOffset();
            int firstNonWS = findEndOfWhiteSpace(d, lineOffset, offset);

            // find out if this is a return after a */ (in which case only add an indent, not a *)
            if (indentCloseToken(d, c, offset, lineOffset, firstNonWS)) {
                return;
            }

            // find out if this is a // style single line comment
            if ((d.getLength() > firstNonWS + 1) && (d.getChar(firstNonWS + 1) == '/')) {
                super.indentAfterNewLine(d, c);
                return;
            }
            // get line prefix
            IPreferenceStore store = JSPlugin.getDefault().getPreferenceStore();
            boolean useStar = true;
            if (store != null) {
                useStar = store.getBoolean(IPreferenceConstants.PREFERENCE_COMMENT_INDENT_USE_STAR);
            }
            linePrefix = useStar ? "* " : " "; //$NON-NLS-1$ //$NON-NLS-2$

            StringBuffer buf = new StringBuffer(c.text);
            IRegion prefix = findPrefixRange(d, line);
            String indentation = d.get(prefix.getOffset(), prefix.getLength());
            // String indentation = getIndentationString(d, lineOffset, firstNonWS);
            // if(indentation == "")
            // return;

            int lengthToAdd = Math.min(offset - prefix.getOffset(), prefix.getLength());

            buf.append(indentation.substring(0, lengthToAdd));

            if (firstNonWS < offset) {
                if (d.getChar(firstNonWS) == '/') {
                    // javadoc started on this line
                    buf.append(" " + linePrefix); //$NON-NLS-1$

                    if (isNewComment(d, offset)) {
                        c.shiftsCaret = false;
                        c.caretOffset = c.offset + buf.length();
                        String lineDelimiter = TextUtilities.getDefaultLineDelimiter(d);

                        String endTag = lineDelimiter + indentation + " */"; //$NON-NLS-1$

                        // guard for end of doc (multiline comment at very end of doc
                        if (d.getLength() > firstNonWS + 2 && d.getChar(firstNonWS + 1) == '*') {
                            // we need to close the comment
                            d.replace(offset, 0, endTag);
                        } else {
                            buf.append(endTag);
                        }
                    }

                }
            }

            // move the caret behind the prefix, even if we do not have to insert it.
            if (lengthToAdd < prefix.getLength()) {
                c.caretOffset = offset + prefix.getLength() - lengthToAdd;
            }
            c.text = buf.toString();

        } catch (BadLocationException excp) {
            // stop work
        }
    }

    /**
     * Guesses if the command operates within a newly created javadoc comment or not. If in doubt,
     * it will assume that the javadoc is new.
     * 
     * @param document
     *            the document
     * @param commandOffset
     *            the command offset
     * @return <code>true</code> if the comment should be closed, <code>false</code> if not
     */
    protected boolean isNewComment(IDocument document, int commandOffset) {

        try {
            // Lexeme lx = lexemeList.get( lexemeList.getLexemeCeilingIndex(commandOffset) );
            // if( lx.getLanguage().equals(ScriptDocMimeType.MimeType) )
            // return false;

            int lineIndex = document.getLineOfOffset(commandOffset) + 1;
            if (lineIndex >= document.getNumberOfLines()) {
                return true;
            }

            IRegion line = document.getLineInformation(lineIndex);

            ITypedRegion partition = TextUtilities.getPartition(document, UnifiedConfiguration.UNIFIED_PARTITIONING,
                    commandOffset, false);
            int partitionEnd = partition.getOffset() + partition.getLength() - 1; // partitions
            // have overlaps
            // in eclipse
            if (line.getOffset() >= partitionEnd) {
                return true;
            }

            String comment = document.get(partition.getOffset(), partition.getLength());
            // comments that don't end with */ are certainly not closed
            if (!comment.endsWith("*/")) //$NON-NLS-1$
            {
                return true;
            }

            int firstNewline = comment.indexOf('\n');

            // assume short comment always unclosed and guard for next test
            if (comment.length() < 4) {
                return true;
            }
            if (comment.startsWith("/**/")) //$NON-NLS-1$
            {
                return false;
            }

            // comments that have * as the first non ws char on next line are probably closed
            String subComment = comment.substring(firstNewline).trim();
            if (subComment.startsWith("*")) //$NON-NLS-1$
            {
                return false;
            }

            // no extra lines means probably not closed (can be a */ line due to previous test)
            if (subComment.indexOf("\n") == -1) //$NON-NLS-1$
            {
                return true;
            }

            // look on the first non ws line for trigger words -
            // this can't be done by counting lexemes instead of text, nor making sure function is a
            // keyword
            // allowing var x.y.z = function syntax
            String firstLine = subComment.substring(0, subComment.indexOf("\n")); //$NON-NLS-1$
            if (firstLine.indexOf("=") > -1) //$NON-NLS-1$
            {
                return true;
            }
            if (firstLine.indexOf("function") > -1) //$NON-NLS-1$
            {
                return true;
            }
            if (firstLine.indexOf("var") > -1) //$NON-NLS-1$
            {
                return true;
            }
            if (comment.indexOf("/*", 2) != -1) //$NON-NLS-1$
            {
                return true; // enclosed another comment -> probably a new comment
            }

            return false;

        } catch (BadLocationException e) {
            return false;
        }
    }

    /**
     * Unindents a typed slash ('/') if it forms the end of a comment.
     * 
     * @param d
     *            the document
     * @param c
     *            the command
     */
    protected void indentAfterCommentEnd(IDocument d, DocumentCommand c) {
        if (c.offset < 2 || d.getLength() == 0) {
            return;
        }
        try {
            if ("* ".equals(d.get(c.offset - 2, 2))) { //$NON-NLS-1$
                // modify document command
                c.length++;
                c.offset--;
            }
        } catch (BadLocationException excp) {
            // stop work
        }
    }

    /**
     * Returns the range of the multiline comment prefix on the given line in <code>document</code>.
     * The prefix greedily matches the following regex pattern: <code>\w*\*\w*</code>, that is,
     * any number of whitespace characters, followed by an asterix ('*'), followed by any number of
     * whitespace characters.
     * 
     * @param document
     *            the document to which <code>line</code> refers
     * @param line
     *            the line from which to extract the prefix range
     * @return an <code>IRegion</code> describing the range of the prefix on the given line
     * @throws BadLocationException
     *             if accessing the document fails
     */
    protected IRegion findPrefixRange(IDocument document, IRegion line) throws BadLocationException {
        int lineOffset = line.getOffset();
        int lineEnd = lineOffset + line.getLength();
        int indentEnd = findEndOfWhiteSpace(document, lineOffset, lineEnd);
        if (indentEnd < lineEnd && document.get(indentEnd, linePrefix.length()).equals(linePrefix)) {
            indentEnd++;
            while (indentEnd < lineEnd && document.getChar(indentEnd) == ' ') {
                indentEnd++;
            }
        }
        return new Region(lineOffset, indentEnd - lineOffset);
    }

    /**
     * @see UnifiedAutoIndentStrategy#getPreferenceStore()
     */
    public IPreferenceStore getPreferenceStore() {
        return JSPlugin.getDefault().getPreferenceStore();
    }

    /**
     * @see com.aptana.ide.editors.unified.UnifiedAutoIndentStrategy#getLexemeList()
     */
    protected LexemeList getLexemeList() {
        JSCommentFileLanguageService ls = (JSCommentFileLanguageService) context
                .getLanguageService(JSCommentMimeType.MimeType);
        return ls.getFileContext().getLexemeList();
    }

}