com.google.dart.tools.ui.internal.text.completion.AbstractDartCompletionProposal.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.tools.ui.internal.text.completion.AbstractDartCompletionProposal.java

Source

/*
 * Copyright (c) 2012, the Dart project authors.
 * 
 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.dart.tools.ui.internal.text.completion;

import com.google.dart.engine.utilities.instrumentation.Instrumentation;
import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder;
import com.google.dart.tools.core.completion.CompletionProposal;
import com.google.dart.tools.core.internal.util.CharOperation;
import com.google.dart.tools.core.model.DartElement;
import com.google.dart.tools.core.model.DartModelException;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.PreferenceConstants;
import com.google.dart.tools.ui.internal.text.dart.SmartSemicolonAutoEditStrategy;
import com.google.dart.tools.ui.internal.text.html.BrowserInformationControl;
import com.google.dart.tools.ui.internal.text.html.HTMLPrinter;
import com.google.dart.tools.ui.text.DartPartitions;
import com.google.dart.tools.ui.text.DartTextTools;
import com.google.dart.tools.ui.text.dart.DartContentAssistInvocationContext;
import com.google.dart.tools.ui.text.dart.IDartCompletionProposal;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.ITextViewerExtension4;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.IContextInformation;
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.viewers.StyledString;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
import org.osgi.framework.Bundle;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

public abstract class AbstractDartCompletionProposal
        implements IDartCompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2,
        ICompletionProposalExtension3, ICompletionProposalExtension5, ICompletionProposalExtension6 {

    /**
     * Allow the linked mode editor to continue running even when the exit character is typed as part
     * of a function argument. Using shift operators in a context that expects balanced angle brackets
     * is not legal syntax and will confuse the linked mode editor.
     */
    protected class ExitPolicy implements IExitPolicy {

        private int parenCount = 0;
        private int braceCount = 0;
        private int bracketCount = 0;
        private int angleBracketCount = 0;
        private char lastChar = (char) 0;

        final char fExitCharacter;
        private final IDocument fDocument;

        public ExitPolicy(char exitCharacter, IDocument document) {
            fExitCharacter = exitCharacter;
            fDocument = document;
        }

        @Override
        public ExitFlags doExit(LinkedModeModel environment, VerifyEvent event, int offset, int length) {
            countGroupChars(event);
            if (event.character == fExitCharacter && isBalanced(fExitCharacter)) {
                if (environment.anyPositionContains(offset)) {
                    return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false);
                } else {
                    return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true);
                }
            }

            switch (event.character) {
            case ';':
                return new ExitFlags(ILinkedModeListener.NONE, true);
            case '\b':
                if (fInvocationContext != null) {
                    if (fInvocationContext.getViewer().getSelectedRange().y > 0) {
                        return new ExitFlags(ILinkedModeListener.EXTERNAL_MODIFICATION, true);
                    }
                }
                return null;
            case SWT.CR:
                // when entering a function as a parameter, we don't want
                // to jump after the parenthesis when return is pressed
                if (offset > 0) {
                    try {
                        if (fDocument.getChar(offset - 1) == '{') {
                            return new ExitFlags(ILinkedModeListener.EXIT_ALL, true);
                        }
                    } catch (BadLocationException e) {
                    }
                }
                return null;
            //        case ',':
            //          // Making comma act like tab seems like a good idea but it requires auto-insert of matching group chars to work.
            //          if (offset > 0) {
            //            try {
            //              if (fDocument.getChar(offset) == ',') {
            //                event.character = 0x09;
            //                return null;
            //              }
            //            } catch (BadLocationException e) {
            //            }
            //          }
            default:
                return null;
            }
        }

        private void countGroupChar(char ch, int inc) {
            switch (ch) {
            case '(':
                parenCount += inc;
                break;
            case ')':
                parenCount -= inc;
                break;
            case '{':
                braceCount += inc;
                break;
            case '}':
                braceCount -= inc;
                break;
            case '[':
                bracketCount += inc;
                break;
            case ']':
                bracketCount -= inc;
                break;
            case '<':
                angleBracketCount += inc;
                break;
            case '>':
                if (lastChar != '=') {
                    // only decrement when not part of =>
                    angleBracketCount -= inc;
                }
                break;
            case '=':
                if (lastChar == '>') {
                    // deleting => should not change angleBracketCount
                    angleBracketCount += inc;
                }
                break;
            default:
                break;
            }
            lastChar = ch;
        }

        private void countGroupChars(VerifyEvent event) {
            char ch = event.character;
            int inc = 1;
            if (ch == '\b') { // TODO Find correct delete chars for Linux & Windows
                inc = -1;
                if (!(event.widget instanceof StyledText)) {
                    return;
                }
                Point sel = ((StyledText) event.widget).getSelection();
                try {
                    if (sel.x == sel.y) {
                        ch = fDocument.getChar(sel.x);
                        countGroupChar(ch, inc);
                    } else {
                        for (int x = sel.y - 1; x >= sel.x; x--) {
                            ch = fDocument.getChar(x);
                            countGroupChar(ch, inc);
                        }
                    }
                } catch (BadLocationException ex) {
                    return;
                }
            } else {
                countGroupChar(ch, inc);
            }
        }

        private boolean isBalanced(char ch) {
            switch (ch) {
            case ')':
                return parenCount == -1;
            case '}':
                return braceCount == -1;
            case ']':
                return bracketCount == -1;
            case '>':
                return angleBracketCount == -1;
            default:
                return true; // never unbalanced
            }
        }
    }

    /**
     * A class to simplify tracking a reference position in a document.
     */
    static final class ReferenceTracker {

        /** The reference position category name. */
        private static final String CATEGORY = "reference_position"; //$NON-NLS-1$
        /** The position updater of the reference position. */
        private final IPositionUpdater fPositionUpdater = new DefaultPositionUpdater(CATEGORY);
        /** The reference position. */
        private final Position fPosition = new Position(0);

        /**
         * Called after the document changed occurred. It must be preceded by a call to preReplace().
         * 
         * @param document the document on which to track the reference position.
         * @return offset after the replace
         */
        public int postReplace(IDocument document) {
            try {
                document.removePosition(CATEGORY, fPosition);
                document.removePositionUpdater(fPositionUpdater);
                document.removePositionCategory(CATEGORY);

            } catch (BadPositionCategoryException e) {
                // should not happen
                DartToolsPlugin.log(e);
            }
            return fPosition.getOffset();
        }

        /**
         * Called before document changes occur. It must be followed by a call to postReplace().
         * 
         * @param document the document on which to track the reference position.
         * @param offset the offset
         * @throws BadLocationException if the offset describes an invalid range in this document
         */
        public void preReplace(IDocument document, int offset) throws BadLocationException {
            fPosition.setOffset(offset);
            try {
                document.addPositionCategory(CATEGORY);
                document.addPositionUpdater(fPositionUpdater);
                document.addPosition(CATEGORY, fPosition);

            } catch (BadPositionCategoryException e) {
                // should not happen
                DartToolsPlugin.log(e);
            }
        }
    }

    /**
     * The control creator.
     */
    private static final class ControlCreator extends AbstractReusableInformationControlCreator {
        @Override
        public IInformationControl doCreateInformationControl(Shell parent) {
            return new BrowserInformationControl(parent, SWT.NO_TRIM | SWT.TOOL, SWT.NONE, null);
        }
    }

    protected static boolean insertCompletion() {
        IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore();
        return preference.getBoolean(PreferenceConstants.CODEASSIST_INSERT_COMPLETION);
    }

    private static Color getBackgroundColor() {
        IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore();
        RGB rgb = PreferenceConverter.getColor(preference, PreferenceConstants.CODEASSIST_REPLACEMENT_BACKGROUND);
        DartTextTools textTools = DartToolsPlugin.getDefault().getDartTextTools();
        return textTools.getColorManager().getColor(rgb);
    }

    private static Color getForegroundColor() {
        IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore();
        RGB rgb = PreferenceConverter.getColor(preference, PreferenceConstants.CODEASSIST_REPLACEMENT_FOREGROUND);
        DartTextTools textTools = DartToolsPlugin.getDefault().getDartTextTools();
        return textTools.getColorManager().getColor(rgb);
    }

    private StyledString fDisplayString;
    private String fReplacementString;
    private int fReplacementOffset;
    private int fReplacementLength;
    private int fCursorPosition;
    private Image fImage;
    private IContextInformation fContextInformation;
    private ProposalInfo fProposalInfo;

    private char[] fTriggerCharacters;

    private String fSortString;
    private int fRelevance;

    private boolean fIsInJavadoc;
    private StyleRange fRememberedStyleRange;

    private boolean fToggleEating;

    private ITextViewer fTextViewer;

    /**
     * The control creator.
     */
    private IInformationControlCreator fCreator;

    /**
     * The CSS used to format javadoc information.
     */
    private static String fgCSSStyles;

    /**
     * The invocation context of this completion proposal. Can be <code>null</code>.
     */
    protected final DartContentAssistInvocationContext fInvocationContext;

    /**
     * Cache to store last validation state.
     */
    private boolean fIsValidated = true;

    /**
     * The text presentation listener.
     */
    private ITextPresentationListener fTextPresentationListener;

    protected AbstractDartCompletionProposal() {
        fInvocationContext = null;
    }

    protected AbstractDartCompletionProposal(DartContentAssistInvocationContext context) {
        fInvocationContext = context;
    }

    @Override
    public final void apply(IDocument document) {
        // not used any longer
        apply(document, (char) 0, getReplacementOffset() + getReplacementLength());
    }

    @Override
    public void apply(IDocument document, char trigger, int offset) {

        InstrumentationBuilder instrumentation = Instrumentation.builder("CompletionProposal-Apply");
        instrumentation.metric("Trigger", trigger);

        try {
            if (isSupportingRequiredProposals()) {
                CompletionProposal coreProposal = ((MemberProposalInfo) getProposalInfo()).fProposal;
                CompletionProposal[] requiredProposals = coreProposal.getRequiredProposals();
                for (int i = 0; requiredProposals != null && i < requiredProposals.length; i++) {
                    int oldLen = document.getLength();
                    if (requiredProposals[i].getKind() == CompletionProposal.TYPE_REF) {
                        LazyDartCompletionProposal proposal = createRequiredTypeCompletionProposal(
                                requiredProposals[i], fInvocationContext);
                        proposal.apply(document);
                        setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen);
                    } else {
                        /*
                         * we only support the above required proposals, see
                         * CompletionProposal#getRequiredProposals()
                         */
                        Assert.isTrue(false);
                    }
                }
            }

            try {
                boolean isSmartTrigger = isSmartTrigger(trigger);
                instrumentation.metric("isSmartTrigger", isSmartTrigger);

                String replacement;
                if (isSmartTrigger || trigger == (char) 0) {
                    replacement = getReplacementString();
                } else {
                    StringBuffer buffer = new StringBuffer(getReplacementString());

                    // fix for PR #5533. Assumes that no eating takes place.
                    if ((getCursorPosition() > 0 && getCursorPosition() <= buffer.length()
                            && buffer.charAt(getCursorPosition() - 1) != trigger)) {
                        buffer.insert(getCursorPosition(), trigger);
                        setCursorPosition(getCursorPosition() + 1);
                    }

                    replacement = buffer.toString();
                    setReplacementString(replacement);
                }

                instrumentation.data("Replacement", replacement);

                // reference position just at the end of the document change.
                int referenceOffset = getReplacementOffset() + getReplacementLength();
                final ReferenceTracker referenceTracker = new ReferenceTracker();
                referenceTracker.preReplace(document, referenceOffset);

                replace(document, getReplacementOffset(), getReplacementLength(), replacement);

                referenceOffset = referenceTracker.postReplace(document);
                int delta = replacement == null ? 0 : replacement.length();
                if (delta > 0 && replacement.charAt(replacement.length() - 1) == ']') {
                    delta += 1;
                }
                setReplacementOffset(referenceOffset - delta);

                // PR 47097
                if (isSmartTrigger) {
                    handleSmartTrigger(document, trigger, referenceOffset);
                }

            } catch (BadLocationException x) {
                instrumentation.metric("Problem", "BadLocationException");
                // ignore
            }
        } finally {
            instrumentation.log();
        }
    }

    @Override
    public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {

        IDocument document = viewer.getDocument();
        if (fTextViewer == null) {
            fTextViewer = viewer;
        }

        // don't apply the proposal if for some reason we're not valid any longer
        if (!isInDartDoc() && !validate(document, offset, null)) {
            setCursorPosition(offset);
            if (trigger != '\0') {
                try {
                    document.replace(offset, 0, String.valueOf(trigger));
                    setCursorPosition(getCursorPosition() + 1);
                    if (trigger == '(' && autocloseBrackets()) {
                        document.replace(getReplacementOffset() + getCursorPosition(), 0, ")"); //$NON-NLS-1$
                        setUpLinkedMode(document, ')');
                    }
                } catch (BadLocationException x) {
                    // ignore
                }
            }
            return;
        }

        // don't eat if not in preferences, XOR with Ctrl
        // but: if there is a selection, replace it!
        Point selection = viewer.getSelectedRange();
        fToggleEating = (stateMask & SWT.CTRL) != 0;
        int newLength = selection.x + selection.y - getReplacementOffset();
        if ((insertCompletion() ^ fToggleEating) && newLength >= 0) {
            setReplacementLength(newLength);
        }

        apply(document, trigger, offset);
        fToggleEating = false;
    }

    @Override
    public String getAdditionalProposalInfo() {
        Object info = getAdditionalProposalInfo(new NullProgressMonitor());
        return info == null ? null : info.toString();
    }

    @Override
    public Object getAdditionalProposalInfo(IProgressMonitor monitor) {
        if (getProposalInfo() != null) {
            String info = getProposalInfo().getInfo(monitor);
            if (info != null && info.length() > 0) {
                StringBuffer buffer = new StringBuffer();
                HTMLPrinter.insertPageProlog(buffer, 0, getCSSStyles());
                buffer.append(info);
                HTMLPrinter.addPageEpilog(buffer);
                info = buffer.toString();
            }
            return info;
        }
        return null;
    }

    @Override
    public IContextInformation getContextInformation() {
        return fContextInformation;
    }

    @Override
    public int getContextInformationPosition() {
        if (getContextInformation() == null) {
            return getReplacementOffset() - 1;
        }
        return getReplacementOffset() + getCursorPosition();
    }

    /**
     * Returns the java element proposed by the receiver, possibly <code>null</code>.
     * 
     * @return the java element proposed by the receiver, possibly <code>null</code>
     */
    public DartElement getDartElement() {
        if (getProposalInfo() != null) {
            try {
                return getProposalInfo().getJavaElement();
            } catch (DartModelException x) {
                DartToolsPlugin.log(x);
            }
        }
        return null;
    }

    @Override
    public String getDisplayString() {
        if (fDisplayString != null) {
            return fDisplayString.getString();
        }
        return ""; //$NON-NLS-1$
    }

    @Override
    public Image getImage() {
        return fImage;
    }

    @Override
    public IInformationControlCreator getInformationControlCreator() {
        Shell shell = DartToolsPlugin.getActiveWorkbenchShell();
        if (shell == null || !BrowserInformationControl.isAvailable(shell)) {
            return null;
        }

        if (fCreator == null) {
            fCreator = new ControlCreator();
        }
        return fCreator;
    }

    @Override
    public int getPrefixCompletionStart(IDocument document, int completionOffset) {
        return getReplacementOffset();
    }

    @Override
    public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
        if (!isCamelCaseMatching()) {
            return getReplacementString();
        }

        String prefix = getPrefix(document, completionOffset);
        return getCamelCaseCompound(prefix, getReplacementString());
    }

    /**
     * Gets the proposal's relevance.
     * 
     * @return Returns an int
     */
    @Override
    public int getRelevance() {
        return fRelevance;
    }

    /**
     * Gets the replacement length.
     * 
     * @return Returns an int
     */
    public int getReplacementLength() {
        return fReplacementLength;
    }

    /**
     * Gets the replacement offset.
     * 
     * @return Returns an int
     */
    public int getReplacementOffset() {
        return fReplacementOffset;
    }

    /**
     * Gets the replacement string.
     * 
     * @return Returns a String
     */
    public String getReplacementString() {
        return fReplacementString;
    }

    @Override
    public Point getSelection(IDocument document) {
        if (!fIsValidated) {
            return null;
        }
        return new Point(getReplacementOffset() + getCursorPosition(), 0);
    }

    public String getSortString() {
        return fSortString;
    }

    @Override
    public StyledString getStyledDisplayString() {
        return fDisplayString;
    }

    @Override
    public char[] getTriggerCharacters() {
        return fTriggerCharacters;
    }

    @Override
    public boolean isValidFor(IDocument document, int offset) {
        return validate(document, offset, null);
    }

    @Override
    public void selected(final ITextViewer viewer, boolean smartToggle) {
        repairPresentation(viewer);
        fRememberedStyleRange = null;

        if (!insertCompletion() ^ smartToggle) {
            StyleRange range = createStyleRange(viewer);
            if (range == null) {
                return;
            }

            fRememberedStyleRange = range;

            if (viewer instanceof ITextViewerExtension4) {
                if (fTextPresentationListener == null) {
                    fTextPresentationListener = new ITextPresentationListener() {
                        @Override
                        public void applyTextPresentation(TextPresentation textPresentation) {
                            fRememberedStyleRange = createStyleRange(viewer);
                            if (fRememberedStyleRange != null) {
                                textPresentation.mergeStyleRange(fRememberedStyleRange);
                            }
                        }
                    };
                    ((ITextViewerExtension4) viewer).addTextPresentationListener(fTextPresentationListener);
                }
                repairPresentation(viewer);
            } else {
                updateStyle(viewer);
            }
        }
    }

    /**
     * Sets the context information.
     * 
     * @param contextInformation The context information associated with this proposal
     */
    public void setContextInformation(IContextInformation contextInformation) {
        fContextInformation = contextInformation;
    }

    /**
     * Sets the cursor position relative to the insertion offset. By default this is the length of the
     * completion string (Cursor positioned after the completion)
     * 
     * @param cursorPosition The cursorPosition to set
     */
    public void setCursorPosition(int cursorPosition) {
        Assert.isTrue(cursorPosition >= 0);
        fCursorPosition = cursorPosition;
    }

    /**
     * Sets the image.
     * 
     * @param image The image to set
     */
    public void setImage(Image image) {
        fImage = image;
    }

    /**
     * Sets the proposal info.
     * 
     * @param proposalInfo The additional information associated with this proposal or
     *          <code>null</code>
     */
    public void setProposalInfo(ProposalInfo proposalInfo) {
        fProposalInfo = proposalInfo;
    }

    /**
     * Sets the proposal's relevance.
     * 
     * @param relevance The relevance to set
     */
    public void setRelevance(int relevance) {
        fRelevance = relevance;
    }

    /**
     * Sets the replacement length.
     * 
     * @param replacementLength The replacementLength to set
     */
    public void setReplacementLength(int replacementLength) {
        Assert.isTrue(replacementLength >= 0);
        fReplacementLength = replacementLength;
    }

    /**
     * Sets the replacement offset.
     * 
     * @param replacementOffset The replacement offset to set
     */
    public void setReplacementOffset(int replacementOffset) {
        Assert.isTrue(replacementOffset >= 0);
        fReplacementOffset = replacementOffset;
    }

    /**
     * Sets the replacement string.
     * 
     * @param replacementString The replacement string to set
     */
    public void setReplacementString(String replacementString) {
        Assert.isNotNull(replacementString);
        fReplacementString = replacementString;
    }

    public void setStyledDisplayString(StyledString text) {
        fDisplayString = text;
    }

    /**
     * Sets the trigger characters.
     * 
     * @param triggerCharacters The set of characters which can trigger the application of this
     *          completion proposal
     */
    public void setTriggerCharacters(char[] triggerCharacters) {
        fTriggerCharacters = triggerCharacters;
    }

    @Override
    public String toString() {
        return getDisplayString();
    }

    @Override
    public void unselected(ITextViewer viewer) {
        if (fTextPresentationListener != null) {
            ((ITextViewerExtension4) viewer).removeTextPresentationListener(fTextPresentationListener);
            fTextPresentationListener = null;
        }
        repairPresentation(viewer);
        fRememberedStyleRange = null;
    }

    @Override
    public boolean validate(IDocument document, int offset, DocumentEvent event) {

        if (!isOffsetValid(offset)) {
            return fIsValidated = false;
        }

        fIsValidated = isValidPrefix(getPrefix(document, offset));

        if (fIsValidated && event != null) {
            // adapt replacement range to document change
            int delta = (event.fText == null ? 0 : event.fText.length()) - event.fLength;
            final int newLength = Math.max(getReplacementLength() + delta, 0);
            setReplacementLength(newLength);
        }

        return fIsValidated;
    }

    protected boolean autocloseBrackets() {
        IPreferenceStore preferenceStore = DartToolsPlugin.getDefault().getPreferenceStore();
        return preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACKETS);
    }

    /**
     * Creates the required type proposal.
     * 
     * @param completionProposal the core completion proposal
     * @param invocationContext invocation context
     * @return the required type completion proposal
     */
    protected LazyDartCompletionProposal createRequiredTypeCompletionProposal(CompletionProposal completionProposal,
            DartContentAssistInvocationContext invocationContext) {
        if (PreferenceConstants.getPreferenceStore()
                .getBoolean(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES)) {
            return (LazyDartCompletionProposal) new FillArgumentNamesCompletionProposalCollector(invocationContext)
                    .createDartCompletionProposal(completionProposal);
        } else {
            return new LazyDartTypeCompletionProposal(completionProposal, invocationContext);
        }
    }

    /**
     * Matches <code>prefix</code> against <code>string</code> and replaces the matched region by
     * prefix. Case is preserved as much as possible. This method returns <code>string</code> if camel
     * case completion is disabled. Examples when camel case completion is enabled:
     * <ul>
     * <li>getCamelCompound("NuPo", "NullPointerException") -> "NuPointerException"</li>
     * <li>getCamelCompound("NuPoE", "NullPointerException") -> "NuPoException"</li>
     * <li>getCamelCompound("hasCod", "hashCode") -> "hasCode"</li>
     * </ul>
     * 
     * @param prefix the prefix to match against
     * @param string the string to match
     * @return a compound of prefix and any postfix taken from <code>string</code>
     */
    protected final String getCamelCaseCompound(String prefix, String string) {
        if (prefix.length() > string.length()) {
            return string;
        }

        // a normal prefix - no camel case logic at all
        String start = string.substring(0, prefix.length());
        if (start.equalsIgnoreCase(prefix)) {
            return string;
        }

        final char[] patternChars = prefix.toCharArray();
        final char[] stringChars = string.toCharArray();

        for (int i = 1; i <= stringChars.length; i++) {
            if (CharOperation.camelCaseMatch(patternChars, 0, patternChars.length, stringChars, 0, i)) {
                return prefix + string.substring(i);
            }
        }

        // Not a camel case match at all.
        // This should not happen -> stay with the default behavior
        return string;
    }

    /**
     * Returns the style information for displaying HTML content.
     * 
     * @return the CSS styles
     */
    protected String getCSSStyles() {
        if (fgCSSStyles == null) {
            Bundle bundle = Platform.getBundle(DartToolsPlugin.getPluginId());
            URL url = bundle.getEntry("/DartdocHoverStyleSheet.css"); //$NON-NLS-1$
            if (url != null) {
                BufferedReader reader = null;
                try {
                    url = FileLocator.toFileURL(url);
                    reader = new BufferedReader(new InputStreamReader(url.openStream()));
                    StringBuffer buffer = new StringBuffer(200);
                    String line = reader.readLine();
                    while (line != null) {
                        buffer.append(line);
                        buffer.append('\n');
                        line = reader.readLine();
                    }
                    fgCSSStyles = buffer.toString();
                } catch (IOException ex) {
                    DartToolsPlugin.log(ex);
                } finally {
                    try {
                        if (reader != null) {
                            reader.close();
                        }
                    } catch (IOException e) {
                    }
                }

            }
        }
        String css = fgCSSStyles;
        if (css != null) {
            FontData fontData = JFaceResources.getFontRegistry()
                    .getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
            css = HTMLPrinter.convertTopLevelFont(css, fontData);
        }
        return css;
    }

    protected int getCursorPosition() {
        return fCursorPosition;
    }

    /**
     * Returns the text in <code>document</code> from {@link #getReplacementOffset()} to
     * <code>offset</code>. Returns the empty string if <code>offset</code> is before the replacement
     * offset or if an exception occurs when accessing the document.
     * 
     * @param document the document
     * @param offset the offset
     * @return the prefix
     */
    protected String getPrefix(IDocument document, int offset) {
        try {
            int length = offset - getReplacementOffset();
            if (length > 0) {
                return document.get(getReplacementOffset(), length);
            }
        } catch (BadLocationException x) {
        }
        return ""; //$NON-NLS-1$
    }

    /**
     * Returns the additional proposal info, or <code>null</code> if none exists.
     * 
     * @return the additional proposal info, or <code>null</code> if none exists
     */
    protected ProposalInfo getProposalInfo() {
        return fProposalInfo;
    }

    protected ITextViewer getTextViewer() {
        return fTextViewer;
    }

    /**
     * Returns true if camel case matching is enabled.
     * 
     * @return <code>true</code> if camel case matching is enabled
     */
    protected boolean isCamelCaseMatching() {
        return true;
    }

    /**
     * Returns <code>true</code> if the proposal is within Dart doc, <code>false</code> otherwise.
     * 
     * @return <code>true</code> if the proposal is within Dart doc, <code>false</code> otherwise
     */
    protected boolean isInDartDoc() {
        return fIsInJavadoc;
    }

    /**
     * Tells whether the user toggled the insert mode by pressing the 'Ctrl' key.
     * 
     * @return <code>true</code> if the insert mode is toggled, <code>false</code> otherwise
     */
    protected boolean isInsertModeToggled() {
        return fToggleEating;
    }

    /**
     * Checks whether the given offset is valid for this proposal.
     * 
     * @param offset the caret offset
     * @return <code>true</code> if the offset is valid for this proposal
     */
    protected boolean isOffsetValid(int offset) {
        return getReplacementOffset() <= offset;
    }

    /**
     * Case insensitive comparison of <code>prefix</code> with the start of <code>string</code>.
     * 
     * @param prefix the prefix
     * @param string the string to look for the prefix
     * @return <code>true</code> if the string begins with the given prefix and <code>false</code> if
     *         <code>prefix</code> is longer than <code>string</code> or the string doesn't start with
     *         the given prefix
     */
    protected boolean isPrefix(String prefix, String string) {
        if (prefix == null || string == null || prefix.length() > string.length()) {
            return false;
        }
        String start = string.substring(0, prefix.length());
        return start.equalsIgnoreCase(prefix) || isCamelCaseMatching()
                && CharOperation.camelCaseMatch(prefix.toCharArray(), string.toCharArray());
    }

    /**
     * Tells whether required proposals are supported by this proposal.
     * 
     * @return <code>true</code> if required proposals are supported by this proposal
     */
    protected boolean isSupportingRequiredProposals() {
        if (fInvocationContext == null) {
            return false;
        }

        ProposalInfo proposalInfo = getProposalInfo();
        if (!(proposalInfo instanceof MemberProposalInfo)) {
            return false;
        }

        CompletionProposal proposal = ((MemberProposalInfo) proposalInfo).fProposal;
        return proposal != null && (proposal.getKind() == CompletionProposal.METHOD_REF
                || proposal.getKind() == CompletionProposal.FIELD_REF
                || proposal.getKind() == CompletionProposal.TYPE_REF
                //          || proposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION
                || proposal.getKind() == CompletionProposal.CONSTRUCTOR_INVOCATION);
    }

    protected boolean isToggleEating() {
        return fToggleEating;
    }

    /**
     * Checks whether <code>prefix</code> is a valid prefix for this proposal. Usually, while code
     * completion is in progress, the user types and edits the prefix in the document in order to
     * filter the proposal list. From {@link #validate(IDocument, int, DocumentEvent) }, the current
     * prefix in the document is extracted and this method is called to find out whether the proposal
     * is still valid.
     * <p>
     * The default implementation checks if <code>prefix</code> is a prefix of the proposal's
     * {@link #getDisplayString() display string} using the {@link #isPrefix(String, String) } method.
     * </p>
     * 
     * @param prefix the current prefix in the document
     * @return <code>true</code> if <code>prefix</code> is a valid prefix of this proposal
     */
    protected boolean isValidPrefix(String prefix) {
        /*
         * See http://dev.eclipse.org/bugs/show_bug.cgi?id=17667 why we do not use the replacement
         * string. String word= fReplacementString;
         * 
         * Besides that bug we also use the display string for performance reasons, as computing the
         * replacement string can be expensive.
         */
        return isPrefix(prefix, TextProcessor.deprocess(getDisplayString()));
    }

    protected final void replace(IDocument document, int offset, int length, String string)
            throws BadLocationException {
        if (!document.get(offset, length).equals(string)) {
            document.replace(offset, length, string);
        }
    }

    protected void setDisplayString(String string) {
        fDisplayString = new StyledString(string);
    }

    /**
     * Sets the Dava doc attribute.
     * 
     * @param isInJavadoc <code>true</code> if the proposal is within javadoc
     */
    protected void setInDartDoc(boolean isInJavadoc) {
        fIsInJavadoc = isInJavadoc;
    }

    protected void setSortString(String string) {
        fSortString = string;
    }

    /**
     * Sets up a simple linked mode at {@link #getCursorPosition()} and an exit policy that will exit
     * the mode when <code>closingCharacter</code> is typed and an exit position at
     * <code>getCursorPosition() + 1</code>.
     * 
     * @param document the document
     * @param closingCharacter the exit character
     */
    protected void setUpLinkedMode(IDocument document, char closingCharacter) {
        if (getTextViewer() != null && autocloseBrackets()) {
            int offset = getReplacementOffset() + getCursorPosition();
            int exit = getReplacementOffset() + getReplacementString().length();
            try {
                LinkedPositionGroup group = new LinkedPositionGroup();
                group.addPosition(new LinkedPosition(document, offset, 0, LinkedPositionGroup.NO_STOP));

                LinkedModeModel model = new LinkedModeModel();
                model.addGroup(group);
                model.forceInstall();

                LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer());
                ui.setSimpleMode(true);
                ui.setExitPolicy(new ExitPolicy(closingCharacter, document));
                ui.setExitPosition(getTextViewer(), exit, 0, Integer.MAX_VALUE);
                ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER);
                ui.enter();
            } catch (BadLocationException x) {
                DartToolsPlugin.log(x);
            }
        }
    }

    /**
     * Creates a style range for the text viewer.
     * 
     * @param viewer the text viewer
     * @return the new style range for the text viewer or <code>null</code>
     */
    private StyleRange createStyleRange(ITextViewer viewer) {
        StyledText text = viewer.getTextWidget();
        if (text == null || text.isDisposed()) {
            return null;
        }

        int widgetCaret = text.getCaretOffset();

        int modelCaret = 0;
        if (viewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
            modelCaret = extension.widgetOffset2ModelOffset(widgetCaret);
        } else {
            IRegion visibleRegion = viewer.getVisibleRegion();
            modelCaret = widgetCaret + visibleRegion.getOffset();
        }

        if (modelCaret >= getReplacementOffset() + getReplacementLength()) {
            return null;
        }

        int length = getReplacementOffset() + getReplacementLength() - modelCaret;

        Color foreground = getForegroundColor();
        Color background = getBackgroundColor();

        return new StyleRange(modelCaret, length, foreground, background);
    }

    @SuppressWarnings("unused")
    private IWorkbenchSite getSite() {
        IWorkbenchPage page = DartToolsPlugin.getActivePage();
        if (page != null) {
            IWorkbenchPart part = page.getActivePart();
            if (part != null) {
                return part.getSite();
            }
        }
        return null;
    }

    /**
     * Convert a document offset to the corresponding widget offset.
     * 
     * @param viewer the text viewer
     * @param documentOffset the document offset
     * @return widget offset
     */
    private int getWidgetOffset(ITextViewer viewer, int documentOffset) {
        if (viewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
            return extension.modelOffset2WidgetOffset(documentOffset);
        }
        IRegion visible = viewer.getVisibleRegion();
        int widgetOffset = documentOffset - visible.getOffset();
        if (widgetOffset > visible.getLength()) {
            return -1;
        }
        return widgetOffset;
    }

    private void handleSmartTrigger(IDocument document, char trigger, int referenceOffset)
            throws BadLocationException {
        DocumentCommand cmd = new DocumentCommand() {
        };

        cmd.offset = referenceOffset;
        cmd.length = 0;
        cmd.text = Character.toString(trigger);
        cmd.doit = true;
        cmd.shiftsCaret = true;
        cmd.caretOffset = getReplacementOffset() + getCursorPosition();

        SmartSemicolonAutoEditStrategy strategy = new SmartSemicolonAutoEditStrategy(
                DartPartitions.DART_PARTITIONING);
        strategy.customizeDocumentCommand(document, cmd);

        replace(document, cmd.offset, cmd.length, cmd.text);
        setCursorPosition(cmd.caretOffset - getReplacementOffset() + cmd.text.length());
    }

    private boolean isSmartTrigger(char trigger) {
        return trigger == ';'
                && DartToolsPlugin.getDefault().getCombinedPreferenceStore()
                        .getBoolean(PreferenceConstants.EDITOR_SMART_SEMICOLON)
                || trigger == '{' && DartToolsPlugin.getDefault().getCombinedPreferenceStore()
                        .getBoolean(PreferenceConstants.EDITOR_SMART_OPENING_BRACE);
    }

    private void repairPresentation(ITextViewer viewer) {
        if (fRememberedStyleRange != null) {
            if (viewer instanceof ITextViewerExtension2) {
                // attempts to reduce the redraw area
                ITextViewerExtension2 viewer2 = (ITextViewerExtension2) viewer;
                viewer2.invalidateTextPresentation(fRememberedStyleRange.start, fRememberedStyleRange.length);
            } else {
                viewer.invalidateTextPresentation();
            }
        }
    }

    private void updateStyle(ITextViewer viewer) {
        StyledText text = viewer.getTextWidget();
        int widgetOffset = getWidgetOffset(viewer, fRememberedStyleRange.start);
        StyleRange range = new StyleRange(fRememberedStyleRange);
        range.start = widgetOffset;
        range.length = fRememberedStyleRange.length;
        StyleRange currentRange = text.getStyleRangeAtOffset(widgetOffset);
        if (currentRange != null) {
            range.strikeout = currentRange.strikeout;
            range.underline = currentRange.underline;
            range.fontStyle = currentRange.fontStyle;
        }

        // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
        try {
            text.setStyleRange(range);
        } catch (IllegalArgumentException x) {
            // catching exception as offset + length might be outside of the text widget
            fRememberedStyleRange = null;
        }
    }

}