Package org.erlide.ui.editors.erl

Source Code of org.erlide.ui.editors.erl.ErlangBracketInserter

package org.erlide.ui.editors.erl;

import java.util.List;
import java.util.Stack;

import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.link.ILinkedModeListener;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedModeUI;
import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags;
import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.services.parsing.ErlToken;
import org.erlide.engine.services.parsing.ScannerException;
import org.erlide.ui.editors.erl.autoedit.SmartTypingPreferencePage;
import org.erlide.ui.internal.ErlideUIPlugin;
import org.erlide.util.ErlLogger;

class ErlangBracketInserter implements VerifyKeyListener, ILinkedModeListener {

    private final ISourceViewer sourceViewer;
    IBracketInserterValidator validator;

    public static class BracketLevel {
        int fOffset;
        int fLength;
        LinkedModeUI fUI;
        Position fFirstPosition;
        Position fSecondPosition;
    }

    private static class ExclusivePositionUpdater implements IPositionUpdater {

        /** The position category. */
        private final String fCategory;

        /**
         * Creates a new updater for the given <code>category</code>.
         *
         * @param category
         *            the new category.
         */
        public ExclusivePositionUpdater(final String category) {
            fCategory = category;
        }

        /*
         * @see
         * org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.
         * text.DocumentEvent)
         */
        @Override
        public void update(final DocumentEvent event) {

            final int eventOffset = event.getOffset();
            final int eventOldLength = event.getLength();
            final int eventNewLength = event.getText() == null ? 0 : event.getText()
                    .length();
            final int deltaLength = eventNewLength - eventOldLength;

            try {
                final Position[] positions = event.getDocument().getPositions(fCategory);

                for (int i = 0; i != positions.length; i++) {

                    final Position position = positions[i];

                    if (position.isDeleted()) {
                        continue;
                    }

                    final int offset = position.getOffset();
                    final int length = position.getLength();
                    final int end = offset + length;

                    if (offset >= eventOffset + eventOldLength) {
                        // position comes after change - shift
                        position.setOffset(offset + deltaLength);
                    } else if (end <= eventOffset) {
                        // position comes way before change - leave alone
                    } else if (offset <= eventOffset
                            && end >= eventOffset + eventOldLength) {
                        // event completely internal to the position - adjust
                        // length
                        position.setLength(length + deltaLength);
                    } else if (offset < eventOffset) {
                        // event extends over end of position - adjust length
                        final int newEnd = eventOffset;
                        position.setLength(newEnd - offset);
                    } else if (end > eventOffset + eventOldLength) {
                        // event extends from before position into it - adjust
                        // offset and length offset becomes end of event, length
                        // adjusted accordingly
                        final int newOffset = eventOffset + eventNewLength;
                        position.setOffset(newOffset);
                        position.setLength(end - newOffset);
                    } else {
                        // event consumes the position - delete it
                        position.delete();
                    }
                }
            } catch (final BadPositionCategoryException e) {
                // ignore and return
            }
        }
    }

    private boolean fCloseBraces = false;
    private boolean fCloseBrackets = false;
    private boolean fCloseStrings = false;
    private boolean fCloseParens = false;
    private boolean fCloseAtoms = false;
    private boolean fEmbraceSelection = true;

    private final String CATEGORY = toString();
    private final IPositionUpdater fUpdater = new ExclusivePositionUpdater(CATEGORY);
    private final Stack<BracketLevel> fBracketLevelStack = new Stack<BracketLevel>();
    private IPreferenceChangeListener fPreferenceChangeListener;

    public void setCloseBracketsEnabled(final boolean enabled) {
        fCloseBrackets = enabled;
    }

    public void setCloseAtomsEnabled(final boolean enabled) {
        fCloseAtoms = enabled;
    }

    public void setCloseParensEnabled(final boolean enabled) {
        fCloseParens = enabled;
    }

    public void setCloseBracesEnabled(final boolean enabled) {
        fCloseBraces = enabled;
    }

    public void setCloseStringsEnabled(final boolean enabled) {
        fCloseStrings = enabled;
    }

    public void setEmbraceSelectionEnabled(final boolean enabled) {
        fEmbraceSelection = enabled;
    }

    /*
     * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt
     * .events.VerifyEvent)
     */
    @Override
    public void verifyKey(final VerifyEvent event) {

        // early pruning to slow down normal typing as little as possible
        if (!event.doit || validator.earlyCancelCheck()) {
            return;
        }
        switch (event.character) {
        case '(':
        case '{':
        case '[':
        case '\'':
        case '\"':
            break;
        default:
            return;
        }

        final IDocument document = sourceViewer.getDocument();

        final Point selection = sourceViewer.getSelectedRange();
        final int offset = selection.x;
        final int length = selection.y;
        try {
            final char prev = document.get(offset - 1, 1).charAt(0);
            final String selStr = fEmbraceSelection ? document.get(offset, length) : "";
            final int kind = getKindOfBracket(document, offset, length);

            if (kind == '(' || kind == '{' || kind == '[') {
                return;
            }

            switch (event.character) {
            case '(':
                if (!fCloseParens || kind == '(') {
                    return;
                }
                break;
            case '[':
                if (!fCloseBrackets || kind == '[') {
                    return;
                }
                break;
            case '{':
                if (!fCloseBraces || kind == '{') {
                    return;
                }
                break;
            case '\'':
                if (!fCloseAtoms || kind == '\'' || prev == '\\') {
                    return;
                }
                break;
            case '"':
                if (!fCloseStrings || kind == '"' || prev == '\\') {
                    return;
                }
                break;
            default:
                return;
            }

            if (!validator.validInput()) {
                return;
            }

            final char character = event.character;
            final char closingCharacter = getPeerCharacter(character);
            updateDocument(document, offset, length, selStr, character, closingCharacter);

            event.doit = false;

        } catch (final BadLocationException e) {
            ErlLogger.error(e);
        } catch (final BadPositionCategoryException e) {
            ErlLogger.error(e);
        }
    }

    private void updateDocumentSelection(final IDocument document, final int offset,
            final int selLength, final char closingCharacter)
            throws BadLocationException, BadPositionCategoryException {
        final BracketLevel level = new BracketLevel();
        fBracketLevelStack.push(level);

        final LinkedPositionGroup group = new LinkedPositionGroup();
        group.addPosition(new LinkedPosition(document, offset + 1, 0,
                LinkedPositionGroup.NO_STOP));

        final LinkedModeModel model = new LinkedModeModel();
        model.addLinkingListener(this);
        model.addGroup(group);
        model.forceInstall();

        level.fOffset = offset;
        level.fLength = 2 + selLength;

        // set up position tracking for our magic peers
        if (fBracketLevelStack.size() == 1) {
            document.addPositionCategory(CATEGORY);
            document.addPositionUpdater(fUpdater);
        }
        level.fFirstPosition = new Position(offset, 1);
        level.fSecondPosition = new Position(offset + 1, 1);
        document.addPosition(CATEGORY, level.fFirstPosition);
        document.addPosition(CATEGORY, level.fSecondPosition);

        level.fUI = new EditorLinkedModeUI(model, sourceViewer);
        level.fUI.setSimpleMode(true);
        level.fUI.setExitPolicy(new ExitPolicy(closingCharacter,
                getEscapeCharacter(closingCharacter), fBracketLevelStack));
        level.fUI.setExitPosition(sourceViewer, offset + 2 + selLength, 0,
                Integer.MAX_VALUE);
        level.fUI.setCyclingMode(LinkedModeUI.CYCLE_NEVER);
        level.fUI.enter();

        final IRegion newSelection = level.fUI.getSelectedRegion();
        sourceViewer.setSelectedRange(newSelection.getOffset(), newSelection.getLength());
    }

    private void updateDocument(final IDocument document, final int offset,
            final int length, final String selStr, final char character,
            final char closingCharacter) throws BadLocationException,
            BadPositionCategoryException {
        final StringBuilder buffer = new StringBuilder();
        buffer.append(character);
        buffer.append(selStr);
        buffer.append(closingCharacter);

        document.replace(offset, length, buffer.toString());

        updateDocumentSelection(document, offset, selStr.length(), closingCharacter);
    }

    private int getKindOfBracket(final IDocument document, final int offset,
            final int length) throws BadLocationException {
        final IRegion endLine = document.getLineInformationOfOffset(offset + length);

        List<ErlToken> tokens = null;
        final int getOffset = offset + length, getLength = endLine.getOffset()
                + endLine.getLength() - getOffset;
        final String str = document.get(getOffset, getLength);
        try {
            tokens = ErlangEngine.getInstance().getSimpleScannerService()
                    .lightScanString(str, 0);
        } catch (final ScannerException e) {
        }

        int kind = ErlToken.KIND_OTHER;
        if (tokens != null && !tokens.isEmpty()) {
            kind = tokens.get(0).getKind();
        } else if (str.length() > 0) {
            kind = str.charAt(0);
        }
        return kind;
    }

    /*
     * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.
     * jface.text.link.LinkedModeModel, int)
     */
    @Override
    @SuppressWarnings("synthetic-access")
    public void left(final LinkedModeModel environment, final int flags) {

        final BracketLevel level = fBracketLevelStack.pop();
        if (flags != ILinkedModeListener.EXTERNAL_MODIFICATION) {
            return;
        }

        // remove brackets
        final IDocument document = sourceViewer.getDocument();
        if (document instanceof IDocumentExtension) {
            final IDocumentExtension extension = (IDocumentExtension) document;
            extension.registerPostNotificationReplace(null,
                    new IDocumentExtension.IReplace() {

                        @Override
                        public void perform(final IDocument d,
                                final IDocumentListener owner) {
                            if ((level.fFirstPosition.isDeleted || level.fFirstPosition.length == 0)
                                    && !level.fSecondPosition.isDeleted
                                    && level.fSecondPosition.offset == level.fFirstPosition.offset) {
                                try {
                                    document.replace(level.fSecondPosition.offset,
                                            level.fSecondPosition.length, ""); //$NON-NLS-1$
                                } catch (final BadLocationException e) {
                                    ErlLogger.error(e);
                                }
                            }

                            if (fBracketLevelStack.size() == 0) {
                                document.removePositionUpdater(fUpdater);
                                try {
                                    document.removePositionCategory(CATEGORY);
                                } catch (final BadPositionCategoryException e) {
                                    ErlLogger.error(e);
                                }
                            }
                        }
                    });
        }

    }

    /*
     * @see org.eclipse.jface.text.link.ILinkedModeListener#suspend(org.eclipse
     * .jface.text.link.LinkedModeModel)
     */
    @Override
    public void suspend(final LinkedModeModel environment) {
    }

    /*
     * @see org.eclipse.jface.text.link.ILinkedModeListener#resume(org.eclipse
     * .jface.text.link.LinkedModeModel, int)
     */
    @Override
    public void resume(final LinkedModeModel environment, final int flags) {
    }

    private static char getPeerCharacter(final char character) {
        switch (character) {
        case '(':
            return ')';
        case ')':
            return '(';
        case '{':
            return '}';
        case '}':
            return '{';
        case '[':
            return ']';
        case ']':
            return '[';
        case '"':
            return '"';
        case '\'':
            return '\'';
        default:
            throw new IllegalArgumentException();
        }
    }

    static char getEscapeCharacter(final char character) {
        switch (character) {
        case '"':
        case '\'':
            return '\\';
        default:
            return 0;
        }
    }

    private class ExitPolicy implements IExitPolicy {

        final char fExitCharacter;
        final char fEscapeCharacter;
        final Stack<BracketLevel> fStack;
        final int fSize;

        public ExitPolicy(final char exitCharacter, final char escapeCharacter,
                final Stack<BracketLevel> stack) {
            fExitCharacter = exitCharacter;
            fEscapeCharacter = escapeCharacter;
            fStack = stack;
            fSize = fStack.size();
        }

        @Override
        @SuppressWarnings("synthetic-access")
        public ExitFlags doExit(final LinkedModeModel model, final VerifyEvent event,
                final int offset, final int length) {

            if (fSize == fStack.size() && !isMasked(offset)) {
                if (event.character == fExitCharacter) {
                    final BracketLevel level = fStack.peek();
                    if (level.fFirstPosition.offset > offset
                            || level.fSecondPosition.offset < offset) {
                        return null;
                    }
                    if (level.fSecondPosition.offset == offset && length == 0) {
                        // don't enter the character if if its the closing
                        // peer
                        return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false);
                    }
                }
                // when entering an anonymous class between the parenthesis', we
                // don't want to jump after the closing parenthesis when return
                // is pressed
                if (event.character == SWT.CR && offset > 0) {
                    final IDocument document = sourceViewer.getDocument();
                    try {
                        if (document.getChar(offset - 1) == '{') {
                            return new ExitFlags(ILinkedModeListener.EXIT_ALL, true);
                        }
                    } catch (final BadLocationException e) {
                    }
                }
            }
            return null;
        }

        @SuppressWarnings("synthetic-access")
        private boolean isMasked(final int offset) {
            final IDocument document = sourceViewer.getDocument();
            try {
                return fEscapeCharacter == document.getChar(offset - 1);
            } catch (final BadLocationException e) {
            }
            return false;
        }
    }

    public ErlangBracketInserter(final ISourceViewer sourceViewer) {
        this.sourceViewer = sourceViewer;
        validator = new IBracketInserterValidator() {
            @Override
            public boolean earlyCancelCheck() {
                return false;
            }

            @Override
            public boolean validInput() {
                return true;
            }
        };
        fPreferenceChangeListener = new PreferenceChangeListener();
    }

    public void configure() {
        configurePreferences();

        if (sourceViewer instanceof ITextViewerExtension) {
            ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(this);
        }
        final IEclipsePreferences node = ErlideUIPlugin.getPrefsNode();
        node.addPreferenceChangeListener(fPreferenceChangeListener);
    }

    private void configurePreferences() {
        final List<Boolean> prefs = SmartTypingPreferencePage
                .getBracketInserterPreferences();
        setCloseAtomsEnabled(prefs.get(SmartTypingPreferencePage.ATOMS));
        setCloseBracketsEnabled(prefs.get(SmartTypingPreferencePage.BRACKETS));
        setCloseStringsEnabled(prefs.get(SmartTypingPreferencePage.STRINGS));
        setCloseBracesEnabled(prefs.get(SmartTypingPreferencePage.BRACES));
        setCloseParensEnabled(prefs.get(SmartTypingPreferencePage.PARENS));
        setEmbraceSelectionEnabled(prefs.get(SmartTypingPreferencePage.EMBRACE_SELECTION));
    }

    public void unconfigure() {
        if (sourceViewer instanceof ITextViewerExtension) {
            ((ITextViewerExtension) sourceViewer).removeVerifyKeyListener(this);
        }
        final IEclipsePreferences node = ErlideUIPlugin.getPrefsNode();
        node.removePreferenceChangeListener(fPreferenceChangeListener);
    }

    void setValidator(final IBracketInserterValidator validator) {
        this.validator = validator;
    }

    private class PreferenceChangeListener implements IPreferenceChangeListener {
        @Override
        public void preferenceChange(final PreferenceChangeEvent event) {
            final String key = event.getKey();
            // ErlLogger.debug("event:: " + key);
            if (key.indexOf('/') != -1
                    && key.split("/")[0]
                            .equals(SmartTypingPreferencePage.SMART_TYPING_KEY)) {
                configurePreferences();
            }
        }
    }

}
TOP

Related Classes of org.erlide.ui.editors.erl.ErlangBracketInserter

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.