com.cisco.yangide.editor.editors.YangAutoIndentStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.cisco.yangide.editor.editors.YangAutoIndentStrategy.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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
 *  
 *******************************************************************************/
package com.cisco.yangide.editor.editors;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
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.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;

import com.cisco.yangide.editor.YangEditorPlugin;
import com.cisco.yangide.editor.editors.text.Symbols;
import com.cisco.yangide.editor.editors.text.YangHeuristicScanner;
import com.cisco.yangide.editor.editors.text.YangIndenter;
import com.cisco.yangide.editor.preferences.YangDocumentSetupParticipant;
import com.cisco.yangide.ui.YangUIPlugin;
import com.cisco.yangide.ui.preferences.YangPreferenceConstants;

/**
 * Auto indent strategy sensitive to brackets.
 *
 * @author Alexey Kholupko
 */
public class YangAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {

    /** The line comment introducer. Value is "{@value} " */
    private static final String LINE_COMMENT = "//"; //$NON-NLS-1$

    private boolean fCloseBrace;
    private boolean fIsSmartIndentAfterNewline;

    private String fPartitioning;
    /**
     * The viewer.
     */
    private final ISourceViewer fViewer;

    /**
     * Creates a new YANG auto indent strategy for the given document partitioning.
     *
     * @param partitioning the document partitioning
     * @param viewer the source viewer that this strategy is attached to
     */
    public YangAutoIndentStrategy(String partitioning, ISourceViewer viewer) {
        fPartitioning = partitioning;
        fViewer = viewer;
    }

    private int getBracketCount(IDocument d, int startOffset, int endOffset, boolean ignoreCloseBrackets)
            throws BadLocationException {

        int bracketCount = 0;
        while (startOffset < endOffset) {
            char curr = d.getChar(startOffset);
            startOffset++;
            switch (curr) {
            case '/':
                if (startOffset < endOffset) {
                    char next = d.getChar(startOffset);
                    if (next == '*') {
                        // a comment starts, advance to the comment end
                        startOffset = getCommentEnd(d, startOffset + 1, endOffset);
                    } else if (next == '/') {
                        // '//'-comment: nothing to do anymore on this line
                        startOffset = endOffset;
                    }
                }
                break;
            case '*':
                if (startOffset < endOffset) {
                    char next = d.getChar(startOffset);
                    if (next == '/') {
                        // we have been in a comment: forget what we read before
                        bracketCount = 0;
                        startOffset++;
                    }
                }
                break;
            case '{':
                bracketCount++;
                ignoreCloseBrackets = false;
                break;
            case '}':
                if (!ignoreCloseBrackets) {
                    bracketCount--;
                }
                break;
            case '"':
            case '\'':
                startOffset = getStringEnd(d, startOffset, endOffset, curr);
                break;
            default:
            }
        }
        return bracketCount;
    }

    // ----------- bracket counting ------------------------------------------------------

    private int getCommentEnd(IDocument d, int offset, int endOffset) throws BadLocationException {
        while (offset < endOffset) {
            char curr = d.getChar(offset);
            offset++;
            if (curr == '*') {
                if (offset < endOffset && d.getChar(offset) == '/') {
                    return offset + 1;
                }
            }
        }
        return endOffset;
    }

    private String getIndentOfLine(IDocument d, int line) throws BadLocationException {
        if (line > -1) {
            int start = d.getLineOffset(line);
            int end = start + d.getLineLength(line) - 1;
            int whiteEnd = findEndOfWhiteSpace(d, start, end);
            return d.get(start, whiteEnd - start);
        } else {
            return ""; //$NON-NLS-1$
        }
    }

    private int getStringEnd(IDocument d, int offset, int endOffset, char ch) throws BadLocationException {
        while (offset < endOffset) {
            char curr = d.getChar(offset);
            offset++;
            if (curr == '\\') {
                // ignore escaped characters
                offset++;
            } else if (curr == ch) {
                return offset;
            }
        }
        return endOffset;
    }

    private void smartIndentAfterClosingBracket(IDocument d, DocumentCommand c) {
        if (c.offset == -1 || d.getLength() == 0) {
            return;
        }

        try {

            int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset);
            int line = d.getLineOfOffset(p);
            int start = d.getLineOffset(line);
            int whiteend = findEndOfWhiteSpace(d, start, c.offset);

            YangHeuristicScanner scanner = new YangHeuristicScanner(d);
            YangIndenter indenter = new YangIndenter(d, scanner);

            // shift only when line does not contain any text up to the closing bracket
            if (whiteend == c.offset) { // evaluate the line with the opening bracket that matches
                // out closing bracket

                int reference = indenter.findReferencePosition(c.offset, false, true, false, false);
                int indLine = d.getLineOfOffset(reference);
                if (indLine != -1 && indLine != line) { // take the indent of the found line
                    StringBuffer replaceText = new StringBuffer(getIndentOfLine(d, indLine));
                    // add the rest of the current line including the just added close bracket
                    replaceText.append(d.get(whiteend, c.offset - whiteend));
                    replaceText.append(c.text); // modify document command
                    c.length += c.offset - start;
                    c.offset = start;
                    c.text = replaceText.toString();
                }
            }
        } catch (BadLocationException e) {
            YangEditorPlugin.log(e);
        }
    }

    private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) {
        if (c.offset < 1 || d.getLength() == 0) {
            return;
        }

        YangHeuristicScanner scanner = new YangHeuristicScanner(d);

        int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset);

        try {
            // current line
            int line = d.getLineOfOffset(p);
            int lineOffset = d.getLineOffset(line);

            // make sure we don't have any leading comments etc.
            if (d.get(lineOffset, p - lineOffset).trim().length() != 0) {
                return;
            }

            // line of last Java code
            int pos = scanner.findNonWhitespaceBackward(p, YangHeuristicScanner.UNBOUND);
            if (pos == -1) {
                return;
            }
            int lastLine = d.getLineOfOffset(pos);

            // only shift if the last java line is further up and is a braceless block candidate
            if (lastLine < line) {

                YangIndenter indenter = new YangIndenter(d, scanner);
                StringBuffer indent = indenter.computeIndentation(p, true);
                String toDelete = d.get(lineOffset, c.offset - lineOffset);
                if (indent != null && !indent.toString().equals(toDelete)) {
                    c.text = indent.append(c.text).toString();
                    c.length += c.offset - lineOffset;
                    c.offset = lineOffset;
                }
            }

        } catch (BadLocationException e) {
            YangEditorPlugin.log(e);
        }

    }

    private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
        YangHeuristicScanner scanner = new YangHeuristicScanner(d);
        YangIndenter indenter = new YangIndenter(d, scanner);
        StringBuffer indent = indenter.computeIndentation(c.offset);
        if (indent == null) {
            indent = new StringBuffer();
        }

        // indent.append("    ");

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

        try {
            int p = (c.offset == docLength ? c.offset - 1 : c.offset);
            int line = d.getLineOfOffset(p);

            StringBuffer buf = new StringBuffer(c.text + indent);

            IRegion reg = d.getLineInformation(line);
            int lineEnd = reg.getOffset() + reg.getLength();

            int contentStart = findEndOfWhiteSpace(d, c.offset, lineEnd);
            c.length = Math.max(contentStart - c.offset, 0);

            int start = reg.getOffset();

            // insert closing brace on new line after an unclosed opening brace
            if (getBracketCount(d, start, c.offset, true) > 0 && closeBrace() && !isClosed(d, c.offset, c.length)) {
                c.caretOffset = c.offset + buf.length();
                c.shiftsCaret = false;

                // copy old content of line behind insertion point to new line

                if (c.offset == 0) {
                    if (lineEnd - contentStart > 0) {
                        c.length = lineEnd - c.offset;
                        buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
                    }
                }

                buf.append(TextUtilities.getDefaultLineDelimiter(d));
                StringBuffer reference = null;
                int nonWS = findEndOfWhiteSpace(d, start, lineEnd);
                if (nonWS < c.offset && d.getChar(nonWS) == '{') {
                    reference = new StringBuffer(d.get(start, nonWS - start));
                } else {
                    reference = indenter.getReferenceIndentation(c.offset);
                }
                if (reference != null) {
                    buf.append(reference);
                }
                buf.append('}');
            }
            // insert extra line upon new line between two braces
            else if (c.offset > start && contentStart < lineEnd && d.getChar(contentStart) == '}') {
                int firstCharPos = scanner.findNonWhitespaceBackward(c.offset - 1, start);
                if (firstCharPos != YangHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') {
                    c.caretOffset = c.offset + buf.length();
                    c.shiftsCaret = false;

                    StringBuffer reference = null;
                    int nonWS = findEndOfWhiteSpace(d, start, lineEnd);
                    if (nonWS < c.offset && d.getChar(nonWS) == '{') {
                        reference = new StringBuffer(d.get(start, nonWS - start));
                    } else {
                        reference = indenter.getReferenceIndentation(c.offset);
                    }

                    buf.append(TextUtilities.getDefaultLineDelimiter(d));

                    if (reference != null) {
                        buf.append(reference);
                    }
                }
            }
            c.text = buf.toString();

        } catch (BadLocationException e) {
            YangEditorPlugin.log(e);
        }
    }

    /*
     * @see org.eclipse.jdt.internal.ui.text.java.JavaAutoIndentStrategy#isClosed
     */
    private boolean isClosed(IDocument document, int offset, int length) {

        return getBlockBalance(document, offset, fPartitioning) <= 0;

    }

    private void smartPaste(IDocument document, DocumentCommand command) {
        int newOffset = command.offset;
        int newLength = command.length;
        String newText = command.text;

        try {
            YangHeuristicScanner scanner = new YangHeuristicScanner(document);
            YangIndenter indenter = new YangIndenter(document, scanner);
            int offset = newOffset;

            // reference position to get the indent from
            int refOffset = indenter.findReferencePosition(offset);
            if (refOffset == YangHeuristicScanner.NOT_FOUND) {
                return;
            }
            int peerOffset = getPeerPosition(document, command);
            peerOffset = indenter.findReferencePosition(peerOffset);
            if (peerOffset != YangHeuristicScanner.NOT_FOUND) {
                refOffset = Math.min(refOffset, peerOffset);
            }

            // eat any WS before the insertion to the beginning of the line
            int firstLine = 1; // don't format the first line per default, as it has other content
            // before it
            IRegion line = document.getLineInformationOfOffset(offset);
            String notSelected = document.get(line.getOffset(), offset - line.getOffset());
            if (notSelected.trim().length() == 0) {
                newLength += notSelected.length();
                newOffset = line.getOffset();
                firstLine = 0;
            }

            // prefix: the part we need for formatting but won't paste
            IRegion refLine = document.getLineInformationOfOffset(refOffset);
            String prefix = document.get(refLine.getOffset(), newOffset - refLine.getOffset());

            // handle the indentation computation inside a temporary document
            Document temp = new Document(prefix + newText);
            DocumentRewriteSession session = temp
                    .startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
            scanner = new YangHeuristicScanner(temp);
            indenter = new YangIndenter(temp, scanner);
            installYangStuff(temp);

            // indent the first and second line
            // compute the relative indentation difference from the second line
            // (as the first might be partially selected) and use the value to
            // indent all other lines.
            boolean isIndentDetected = false;
            StringBuffer addition = new StringBuffer();
            int insertLength = 0;
            int firstLineInsertLength = 0;
            int firstLineIndent = 0;
            int first = document.computeNumberOfLines(prefix) + firstLine; // don't format first
            // line
            int lines = temp.getNumberOfLines();
            int tabLength = getVisualTabLengthPreference();
            boolean changed = false;
            for (int l = first; l < lines; l++) { // we don't change the number of lines while
                // adding indents

                IRegion r = temp.getLineInformation(l);
                int lineOffset = r.getOffset();
                int lineLength = r.getLength();

                if (lineLength == 0) {
                    continue;
                }

                if (!isIndentDetected) {

                    // indent the first pasted line
                    String current = getCurrentIndent(temp, l);
                    StringBuffer correct = indenter.computeIndentation(lineOffset);
                    if (correct == null) {
                        return; // bail out
                    }

                    insertLength = subtractIndent(correct, current, addition, tabLength);
                    if (l == first) {
                        firstLineInsertLength = insertLength;
                        firstLineIndent = current.length();
                    }
                    if (l != first && temp.get(lineOffset, lineLength).trim().length() != 0) {
                        isIndentDetected = true;
                        if (firstLineIndent >= current.length()) {
                            insertLength = firstLineInsertLength;
                        }
                        if (insertLength == 0) {
                            // no adjustment needed, bail out
                            if (firstLine == 0) {
                                // but we still need to adjust the first line
                                command.offset = newOffset;
                                command.length = newLength;
                                if (changed) {
                                    break; // still need to get the leading indent of the first line
                                }
                            }
                            return;
                        }
                    } else {
                        changed = insertLength != 0;
                    }
                }

                // relatively indent all pasted lines
                if (insertLength > 0) {
                    addIndent(temp, l, addition, tabLength);
                } else if (insertLength < 0) {
                    cutIndent(temp, l, -insertLength, tabLength);
                }

            }

            removeYangStuff(temp);
            temp.stopRewriteSession(session);
            newText = temp.get(prefix.length(), temp.getLength() - prefix.length());

            command.offset = newOffset;
            command.length = newLength;
            command.text = newText;

        } catch (BadLocationException e) {
            YangEditorPlugin.log(e);
        }

    }

    private void installYangStuff(Document temp) {
        YangDocumentSetupParticipant setupParticipant = new YangDocumentSetupParticipant();
        setupParticipant.setup(temp);

    }

    private static void removeYangStuff(Document document) {
        document.setDocumentPartitioner(YangDocumentSetupParticipant.YANG_PARTITIONING, null);
    }

    /**
     * Returns the indentation of the line <code>line</code> in <code>document</code>. The returned
     * string may contain pairs of leading slashes that are considered part of the indentation.
     */
    private static String getCurrentIndent(Document document, int line) throws BadLocationException {
        IRegion region = document.getLineInformation(line);
        int from = region.getOffset();
        int endOffset = region.getOffset() + region.getLength();

        // go behind line comments
        int to = from;
        while (to < endOffset - 2 && document.get(to, 2).equals(LINE_COMMENT)) {
            to += 2;
        }

        while (to < endOffset) {
            char ch = document.getChar(to);
            if (!Character.isWhitespace(ch)) {
                break;
            }
            to++;
        }

        // don't count the space before javadoc like, asterisk-style comment lines
        if (to > from && to < endOffset - 1 && document.get(to - 1, 2).equals(" *")) { //$NON-NLS-1$
            String type = TextUtilities.getContentType(document, YangDocumentSetupParticipant.YANG_PARTITIONING, to,
                    true);
            if (type.equals(YangPartitionScanner.YANG_COMMENT)) {
                to--;
            }
        }

        return document.get(from, to - from);
    }

    /**
     * Computes the difference of two indentations and returns the difference in length of current
     * and correct. If the return value is positive, <code>addition</code> is initialized with a
     * substring of that length of <code>correct</code>.
     */
    private int subtractIndent(CharSequence correctIndentation, CharSequence currentIndentation,
            StringBuffer difference, int tabLength) {
        int c1 = computeVisualLength(correctIndentation, tabLength);
        int c2 = computeVisualLength(currentIndentation, tabLength);
        int diff = c1 - c2;
        if (diff <= 0) {
            return diff;
        }

        difference.setLength(0);
        int len = 0, i = 0;
        while (len < diff) {
            char c = correctIndentation.charAt(i++);
            difference.append(c);
            len += computeVisualLength(c, tabLength);
        }

        return diff;
    }

    /**
     * Indents line <code>line</code> in <code>document</code> with <code>indent</code>. Leaves
     * leading comment signs alone.
     */
    private void addIndent(Document document, int line, CharSequence indent, int tabLength)
            throws BadLocationException {
        IRegion region = document.getLineInformation(line);
        int insert = region.getOffset();
        int endOffset = region.getOffset() + region.getLength();

        // Compute insert after all leading line comment markers
        int newInsert = insert;
        while (newInsert < endOffset - 2 && document.get(newInsert, 2).equals(LINE_COMMENT)) {
            newInsert += 2;
        }

        // Heuristic to check whether it is commented code or just a comment
        if (newInsert > insert) {
            int whitespaceCount = 0;
            int i = newInsert;
            while (i < endOffset - 1) {
                char ch = document.get(i, 1).charAt(0);
                if (!Character.isWhitespace(ch)) {
                    break;
                }
                whitespaceCount = whitespaceCount + computeVisualLength(ch, tabLength);
                i++;
            }

            // TODO
            if (whitespaceCount != 0) {
                // CodeFormatterUtil.getIndentWidth(fProject))
                insert = newInsert;
            }
        }

        // Insert indent
        document.replace(insert, 0, indent.toString());
    }

    /**
     * Cuts the visual equivalent of <code>toDelete</code> characters out of the indentation of line
     * <code>line</code> in <code>document</code>. Leaves leading comment signs alone.
     */
    private void cutIndent(Document document, int line, int toDelete, int tabLength) throws BadLocationException {
        IRegion region = document.getLineInformation(line);
        int from = region.getOffset();
        int endOffset = region.getOffset() + region.getLength();

        // go behind line comments
        while (from < endOffset - 2 && document.get(from, 2).equals(LINE_COMMENT)) {
            from += 2;
        }

        int to = from;
        while (toDelete > 0 && to < endOffset) {
            char ch = document.getChar(to);
            if (!Character.isWhitespace(ch)) {
                break;
            }
            toDelete -= computeVisualLength(ch, tabLength);
            if (toDelete >= 0) {
                to++;
            } else {
                break;
            }
        }

        document.replace(from, to - from, ""); //$NON-NLS-1$
    }

    /**
     * Returns the visual length of a given <code>CharSequence</code> taking into account the visual
     * tabulator length.
     */
    private int computeVisualLength(CharSequence seq, int tabLength) {
        int size = 0;

        for (int i = 0; i < seq.length(); i++) {
            char ch = seq.charAt(i);
            if (ch == '\t') {
                if (tabLength != 0) {
                    size += tabLength - size % tabLength;
                    // else: size stays the same
                }
            } else {
                size++;
            }
        }
        return size;
    }

    /**
     * Returns the visual length of a given character taking into account the visual tabulator
     * length.
     */
    private int computeVisualLength(char ch, int tabLength) {
        if (ch == '\t') {
            return tabLength;
        } else {
            return 1;
        }
    }

    /**
     * The preference setting for the visual tabulator display.
     */
    private int getVisualTabLengthPreference() {
        return YangEditorPlugin.getDefault().getCombinedPreferenceStore()
                .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
    }

    private boolean isLineDelimiter(IDocument document, String text) {
        String[] delimiters = document.getLegalLineDelimiters();
        if (delimiters != null) {
            return TextUtilities.equals(delimiters, text) > -1;
        }
        return false;
    }

    private void smartIndentOnKeypress(IDocument document, DocumentCommand command) {
        switch (command.text.charAt(0)) {
        case '}':
            smartIndentAfterClosingBracket(document, command);
            break;
        case '{':
            smartIndentAfterOpeningBracket(document, command);
            break;
        }
    }

    /*
     * @see
     * org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text
     * .IDocument, org.eclipse.jface.text.DocumentCommand)
     */
    @Override
    public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
        if (c.doit == false) {
            return;
        }

        clearCachedValues();

        if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
            if (fIsSmartIndentAfterNewline) {
                smartIndentAfterNewLine(d, c);
            }
        } else if (c.text.length() == 1) {
            smartIndentOnKeypress(d, c);
        } else if (c.text.length() > 1 && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE)) {
            if (fViewer == null || fViewer.getTextWidget() == null
                    || !fViewer.getTextWidget().getBlockSelection()) {
                smartPaste(d, c); // no smart backspace for paste
            }
        }
    }

    private static IPreferenceStore getPreferenceStore() {
        return YangUIPlugin.getDefault().getPreferenceStore();
    }

    private boolean closeBrace() {
        return fCloseBrace;
    }

    private void clearCachedValues() {
        IPreferenceStore preferenceStore = getPreferenceStore();
        fCloseBrace = preferenceStore.getBoolean(YangPreferenceConstants.EDITOR_CLOSE_BRACES);
        // fIsSmartTab = preferenceStore.getBoolean(YangPreferenceConstants.EDITOR_SMART_TAB);
        fIsSmartIndentAfterNewline = preferenceStore
                .getBoolean(YangPreferenceConstants.EDITOR_SMART_INDENT_AFTER_NEWLINE);
        // fIsSmartMode = computeSmartMode();
    }

    // private boolean computeSmartMode() {
    // IWorkbenchPage page = YangEditorPlugin.getActivePage();
    // if (page != null) {
    // IEditorPart part = page.getActiveEditor();
    // if (part instanceof ITextEditorExtension3) {
    // ITextEditorExtension3 extension = (ITextEditorExtension3) part;
    // return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
    // }
    // }
    // return false;
    // }

    /**
     * Returns the block balance, i.e. zero if the blocks are balanced at <code>offset</code>, a
     * negative number if there are more closing than opening braces, and a positive number if there
     * are more opening than closing braces.
     */
    private static int getBlockBalance(IDocument document, int offset, String partitioning) {
        if (offset < 1) {
            return -1;
        }
        if (offset >= document.getLength()) {
            return 1;
        }

        int begin = offset;
        int end = offset - 1;

        YangHeuristicScanner scanner = new YangHeuristicScanner(document);

        while (true) {
            begin = scanner.findOpeningPeer(begin - 1, '{', '}');
            end = scanner.findClosingPeer(end + 1, '{', '}');
            if (begin == -1 && end == -1) {
                return 0;
            }
            if (begin == -1) {
                return -1;
            }
            if (end == -1) {
                return 1;
            }
        }
    }

    private int getPeerPosition(IDocument document, DocumentCommand command) {
        if (document.getLength() == 0) {
            return 0;
        }
        /*
         * Search for scope closers in the pasted text and find their opening peers in the document.
         */
        Document pasted = new Document(command.text);
        installYangStuff(pasted);
        int firstPeer = command.offset;

        YangHeuristicScanner pScanner = new YangHeuristicScanner(pasted);
        YangHeuristicScanner dScanner = new YangHeuristicScanner(document);

        // add scope relevant after context to peer search
        int afterToken = dScanner.nextToken(command.offset + command.length, YangHeuristicScanner.UNBOUND);
        try {
            switch (afterToken) {
            case Symbols.TokenRBRACE:
                pasted.replace(pasted.getLength(), 0, "}"); //$NON-NLS-1$
                break;
            case Symbols.TokenRPAREN:
                pasted.replace(pasted.getLength(), 0, ")"); //$NON-NLS-1$
                break;
            case Symbols.TokenRBRACKET:
                pasted.replace(pasted.getLength(), 0, "]"); //$NON-NLS-1$
                break;
            }
        } catch (BadLocationException e) {
            // cannot happen
            Assert.isTrue(false);
        }

        int pPos = 0; // paste text position (increasing from 0)
        int dPos = Math.max(0, command.offset - 1); // document position (decreasing from paste
        // offset)
        while (true) {
            int token = pScanner.nextToken(pPos, YangHeuristicScanner.UNBOUND);
            pPos = pScanner.getPosition();
            switch (token) {
            case Symbols.TokenLBRACE:
            case Symbols.TokenLBRACKET:
            case Symbols.TokenLPAREN:
                pPos = skipScope(pScanner, pPos, token);
                if (pPos == YangHeuristicScanner.NOT_FOUND) {
                    return firstPeer;
                }
                break; // closed scope -> keep searching
            case Symbols.TokenRBRACE:
                int peer = dScanner.findOpeningPeer(dPos, '{', '}');
                dPos = peer - 1;
                if (peer == YangHeuristicScanner.NOT_FOUND) {
                    return firstPeer;
                }
                firstPeer = peer;
                break; // keep searching
            case Symbols.TokenRBRACKET:
                peer = dScanner.findOpeningPeer(dPos, '[', ']');
                dPos = peer - 1;
                if (peer == YangHeuristicScanner.NOT_FOUND) {
                    return firstPeer;
                }
                firstPeer = peer;
                break; // keep searching
            case Symbols.TokenRPAREN:
                peer = dScanner.findOpeningPeer(dPos, '(', ')');
                dPos = peer - 1;
                if (peer == YangHeuristicScanner.NOT_FOUND) {
                    return firstPeer;
                }
                firstPeer = peer;
                break; // keep searching
            case Symbols.TokenEOF:
                return firstPeer;
            default:
                // keep searching
            }
        }
    }

    /**
     * Skips the scope opened by <code>token</code>.
     */
    private static int skipScope(YangHeuristicScanner scanner, int startPosition, int token) {
        int openToken = token;
        int closeToken;
        switch (token) {
        case Symbols.TokenLPAREN:
            closeToken = Symbols.TokenRPAREN;
            break;
        case Symbols.TokenLBRACKET:
            closeToken = Symbols.TokenRBRACKET;
            break;
        case Symbols.TokenLBRACE:
            closeToken = Symbols.TokenRBRACE;
            break;
        default:
            Assert.isTrue(false);
            return -1; // dummy
        }

        int depth = 1;
        int p = startPosition;

        while (true) {
            int tok = scanner.nextToken(p, YangHeuristicScanner.UNBOUND);
            p = scanner.getPosition();

            if (tok == openToken) {
                depth++;
            } else if (tok == closeToken) {
                depth--;
                if (depth == 0) {
                    return p + 1;
                }
            } else if (tok == Symbols.TokenEOF) {
                return YangHeuristicScanner.NOT_FOUND;
            }
        }
    }

}