com.aptana.ide.editors.unified.UnifiedAutoIndentStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.ide.editors.unified.UnifiedAutoIndentStrategy.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.editors.unified;

import java.util.Arrays;

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

import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.formatting.UnifiedBracketInserterBase;
import com.aptana.ide.editors.preferences.IPreferenceConstants;
import com.aptana.ide.lexer.LexemeList;

/**
 * UnifiedAutoIndentStrategy
 */
public abstract class UnifiedAutoIndentStrategy implements IAutoEditStrategy, IPreferenceClient {
    /**
     * configuration
     */
    protected SourceViewerConfiguration configuration;

    /**
     * sourceViewer
     */
    protected ISourceViewer sourceViewer;

    /**
     * context
     */
    protected EditorFileContext context;

    /**
     * spaces
     */
    private String spaces = "                                                                            "; //$NON-NLS-1$

    /**
     * Creates a new instance of the JSAutoEditStrategy
     * 
     * @param context
     * @param configuration
     * @param sourceViewer
     */
    public UnifiedAutoIndentStrategy(EditorFileContext context, SourceViewerConfiguration configuration,
            ISourceViewer sourceViewer) {
        this.context = context;
        this.configuration = configuration;
        this.sourceViewer = 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();
                }
            }
        } else if (command.text.length() == 1 && isAutoInsertCharacter(command.text.charAt(0))
                && isAutoInsertEnabled() && isValidAutoInsertLocation(document, command)) {
            char current = command.text.charAt(0);

            if (overwriteBracket(current, document, command, getLexemeList())) {
                return;
            }
        }
    }

    /**
     * 
     * @param c
     * @return
     */
    private boolean isAutoInsertCharacter(char c) {
        int val = Arrays.binarySearch(getAutoInsertCharacters(), c);
        return val >= 0;
    }

    /**
     * getAutoInsertCharacters
     * 
     * @return char[]
     */
    protected char[] getAutoInsertCharacters() {
        return new char[] { '(', '<', '[', '"', '\'', '{' };
    }

    /**
     * getAutoOverwriteCharacters
     * 
     * @return char[]
     */
    protected char[] getAutoOverwriteCharacters() {
        return new char[] { ')', '>', ']', '"', '\'', '}' };
    }

    /**
     * isAutoInsertEnabled
     * 
     * @return boolean
     */
    protected boolean isAutoInsertEnabled() {
        IPreferenceStore store = getPreferenceStore();
        String abi = com.aptana.ide.editors.preferences.IPreferenceConstants.AUTO_BRACKET_INSERTION;

        return (store == null || store.getString(abi).equals("NONE") == false); //$NON-NLS-1$

        //      if (store != null && store.getString(abi).equals("NONE"))
        //      {
        //         return false;
        //      }
        //      else
        //      {
        //         return true;
        //      }
    }

    /**
     * isValidAutoInsertLocation
     * 
     * @param d
     * @param c
     * @return boolean
     */
    protected boolean isValidAutoInsertLocation(IDocument d, DocumentCommand c) {
        return true;
    }

    /**
     * indentAfterNewLine
     * 
     * @param d
     * @param c
     */
    protected void indentAfterNewLine(IDocument d, DocumentCommand c) {
        String indentString = getIndentString();

        // nothing to add if nothing to add
        if (indentString.equals(StringUtils.EMPTY)) {
            return;
        }

        int offset = c.offset;

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

        c.text += getIndentationAtOffset(d, offset);

        return;
    }

    /**
     * overwriteBracket
     * 
     * @param bracket
     * @param document
     * @param command
     * @param ll
     * @return boolean
     */
    public boolean overwriteBracket(char bracket, IDocument document, DocumentCommand command, LexemeList ll) {
        // if next character is "closing" char, overwrite
        if (canOverwriteBracket(bracket, command.offset, document, ll)) {
            command.text = StringUtils.EMPTY;
            command.shiftsCaret = false;
            command.caretOffset = command.offset + 1;
            return true;
        }

        return false;
    }

    /**
     * canOverwriteBracket
     * 
     * @param bracket
     * @param offset
     * @param document
     * @param ll
     * @return boolean
     */
    public boolean canOverwriteBracket(char bracket, int offset, IDocument document, LexemeList ll) {
        if (offset < document.getLength()) {
            char[] autoOverwriteChars = getAutoOverwriteCharacters();
            Arrays.sort(autoOverwriteChars);

            if (Arrays.binarySearch(autoOverwriteChars, bracket) < 0) {
                return false;
            }

            // If the next char is a ">", our tag is already closed
            try {
                char sibling = document.getChar(offset);
                return sibling == bracket;
            } catch (BadLocationException ex) {
                return false;
            }
        }

        return false;
    }

    /**
     * closeBracket
     * 
     * @param bracket
     * @param document
     * @param command
     * @return boolean
     */
    public boolean closeBracket(char bracket, IDocument document, DocumentCommand command) {

        if (!canCloseBracket(bracket, document)) {
            return false;
        } else {
            command.text = Character.toString(bracket)
                    + Character.toString(UnifiedBracketInserterBase.getPeerCharacter(bracket));
            command.shiftsCaret = false;
            command.caretOffset = command.offset + 1;
            return true;
        }
    }

    /**
     * canCloseBracket
     * 
     * @param bracket
     * @param document
     * @return boolean
     */
    public boolean canCloseBracket(char bracket, IDocument document) {
        if (!UnifiedBracketInserterBase.hasPeerCharacter(bracket)) {
            return false;
        }

        char[] autoInsertChars = getAutoInsertCharacters();
        Arrays.sort(autoInsertChars);

        if (Arrays.binarySearch(autoInsertChars, bracket) < 0) {
            return false;
        }

        return !UnifiedBracketInserterBase.isStringBalanced(document.get(), bracket, true);
    }

    /**
     * getIndentationAtOffset
     * 
     * @param d
     * @param offset
     * @return String
     */
    protected String getIndentationAtOffset(IDocument d, int offset) {
        String indentation = StringUtils.EMPTY;
        try {
            int p = (offset == d.getLength() ? offset - 1 : offset);
            IRegion line = d.getLineInformationOfOffset(p);

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

            indentation = getIndentationString(d, lineOffset, firstNonWS);

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

        return indentation;
    }

    /**
     * getIndentString
     * 
     * @return String
     */
    protected String getIndentString() {
        String[] indents = this.configuration.getIndentPrefixes(this.sourceViewer,
                this.context.getDefaultLanguage());
        boolean hasIndents = !((indents == null) || (indents.length == 0));
        String indentString = hasIndents ? indents[0] : "\t"; //$NON-NLS-1$
        return indentString;

    }

    /**
     * Calculates the whitespace prefix based on user prefs and the existing line. Eg: if the line prefix is five
     * spaces, and user pref is tabs of width 4, then the result is "/t ".
     * 
     * @param d
     * @param lineOffset
     * @param firstNonWS
     * @return Returns the whitespace prefix based on user prefs and the existing line.
     */
    protected String getIndentationString(IDocument d, int lineOffset, int firstNonWS) {
        String lineIndent = StringUtils.EMPTY;
        try {
            lineIndent = d.get(lineOffset, firstNonWS - lineOffset);
        } catch (BadLocationException e1) {
        }
        if (lineIndent.equals(StringUtils.EMPTY)) {
            return lineIndent;
        }

        int indentSize = 0;
        int tabWidth = this.configuration.getTabWidth(sourceViewer);
        char[] indentChars = lineIndent.toCharArray();
        for (int i = 0; i < indentChars.length; i++) {
            char e = indentChars[i];
            if (e == '\t') {
                indentSize += tabWidth - (indentSize % tabWidth);
            } else {
                indentSize++;
            }
        }
        String indentString = getIndentString();
        int indentStringWidth = (indentString.equals("\t")) ? tabWidth : indentString.length(); //$NON-NLS-1$
        // return in case tab width is zero
        if (indentStringWidth == 0) {
            return StringUtils.EMPTY;
        }
        int indentCount = (int) Math.floor(indentSize / indentStringWidth); // assume no dived by zero from above tests

        String indentation = StringUtils.EMPTY;
        for (int i = 0; i < indentCount; i++) {
            indentation += indentString;
        }
        // here we might want to allow one tab when there are three spaces on the previous line when tabwdith = 4
        // logic is just get the ending from the previous line
        int extra = indentSize % indentStringWidth;
        indentation += spaces.substring(0, extra);// lineIndent.substring(lineIndent.length() - extra);

        return indentation;
    }

    /**
     * Copies the indentation of the previous line.
     * 
     * @param d
     *            the document to work on
     * @param c
     *            the command to deal with
     * @return String
     */
    protected String getIndentForCurrentLine(IDocument d, DocumentCommand c) {

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

        try {
            // find start of line
            int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset);
            IRegion info = d.getLineInformationOfOffset(p);
            int start = info.getOffset();

            // find white spaces
            int end = findEndOfWhiteSpace(d, start, c.offset);

            StringBuffer buf = new StringBuffer();
            if (end > start) {
                // append to input
                buf.append(d.get(start, end - start));
            }

            return buf.toString();

        } catch (BadLocationException excp) {
        }

        return StringUtils.EMPTY;

    }

    /**
     * Hmm, this is copied from eclipses DefaultIndentAutoIndentStategy, but only accounts for spaces or tabs (unlike
     * the whole insert strings allowing any string).
     * 
     * @param document
     * @param offset
     * @param end
     * @return end of whitespace
     * @throws BadLocationException
     */
    protected int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
        while (offset < end) {
            char c = document.getChar(offset);
            if (c != ' ' && c != '\t') {
                return offset;
            }
            offset++;
        }
        return end;
    }

    /**
     * Returns the current preference store
     * 
     * @return The current preference store, or null if not found
     */
    public abstract IPreferenceStore getPreferenceStore();

    /**
     * Creates a lexeme list
     * 
     * @return LexemeList
     */
    protected abstract LexemeList getLexemeList();

    /**
     * Handles the case where we just added a brace, and we want to return and indent to the next line
     * 
     * @param d
     *            Document
     * @param command
     *            DocumentCommand
     * @return True if it succeeded
     */
    protected boolean indentAfterOpenBrace(IDocument d, DocumentCommand command) {
        int offset = command.offset;
        boolean result = false;

        if (offset != -1 && d.getLength() != 0) {
            String indent = getIndentForCurrentLine(d, command);
            String newline = command.text;
            String tab = "\t"; //$NON-NLS-1$

            if (configuration instanceof UnifiedConfiguration) {
                UnifiedConfiguration uc = (UnifiedConfiguration) configuration;

                tab = uc.getIndent();
            }

            try {
                if (command.offset > 0) {
                    char c = d.getChar(command.offset - 1);

                    if (c == '{') {
                        String startIndent = newline + indent + tab;

                        if (command.offset < d.getLength() && d.getChar(command.offset) == '}') {
                            command.text = startIndent + newline + indent;
                        } else {
                            command.text = startIndent;
                        }

                        command.shiftsCaret = false;
                        command.caretOffset = command.offset + startIndent.length();

                        result = true;
                    }
                }
            } catch (BadLocationException e) {
                IdeLog.logError(UnifiedEditorsPlugin.getDefault(),
                        StringUtils.format(Messages.UnifiedAutoIndentStrategy_InvalidOffset, offset), e);
            }
        }

        return result;
    }

    /**
     * Forces code assist to appear
     */
    protected void triggerContentAssistPopup() {
        if (sourceViewer instanceof SourceViewer) {
            if (!autoTriggerAssist()) {
                return;
            }

            ((SourceViewer) sourceViewer).doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);
        }
    }

    /**
     * Forces code assist to appear
     */
    protected void hideContentAssistPopup() {
        if (sourceViewer instanceof UnifiedViewer) {
            ((UnifiedViewer) sourceViewer).closeContentAssist();
        }
    }

    /**
     * Forces context assist to appear
     */
    protected void triggerContextAssistPopup() {
        if (sourceViewer instanceof SourceViewer) {
            if (!autoTriggerAssist()) {
                return;
            }

            ((SourceViewer) sourceViewer).doOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION);
        }
    }

    /**
     * Do we auto-trigger content/context assist?
     * @return
     */
    protected boolean autoTriggerAssist() {
        // only auto-pop if the preferences allow it
        return (getPreferenceStore() != null
                && getPreferenceStore().getBoolean(IPreferenceConstants.CODE_ASSIST_AUTO_ACTIVATION));
    }

}