org.eclipse.wst.sse.ui.contentassist.StructuredContentAssistProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wst.sse.ui.contentassist.StructuredContentAssistProcessor.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2013 IBM Corporation 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.sse.ui.contentassist;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionListenerExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension2;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension3;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.internal.ExtendedConfigurationBuilder;
import org.eclipse.wst.sse.ui.internal.IReleasable;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.SSEUIMessages;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalCategory;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalComputerRegistry;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposoalCatigoriesConfigurationRegistry;
import org.eclipse.wst.sse.ui.internal.contentassist.CompoundContentAssistProcessor;
import org.eclipse.wst.sse.ui.internal.contentassist.ContextInformationValidator;
import org.eclipse.wst.sse.ui.internal.contentassist.OptionalMessageDialog;
import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationReader;
import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationWriter;

/**
 * <p>A content assist processor that aggregates the proposals of the
 * {@link org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer}s contributed via the
 * <code>org.eclipse.wst.sse.ui.completionProposal</code> extension point.</p>
 * <p>
 * Extenders may extend:
 * <ul>
 * <li>{@link #propertyChange(PropertyChangeEvent)}to react to property change events
 * that occur in the {@link IPreferenceStore} given to the constructor in case the behavior
 * of the processor needs to change according to user preferences</li>
 * <li>{@link #getCompletionProposalAutoActivationCharacters()}</li>
 * <li>{@link #getContextInformationAutoActivationCharacters()}</li>
 * <li>{@link #filterAndSortProposals(List, IProgressMonitor, CompletionProposalInvocationContext)}
 * to add sorting and filtering</li>
 * <li>{@link #filterAndSortContextInformation(List, IProgressMonitor)} to add sorting and filtering</li>
 * <li>{@link #createProgressMonitor()} to change the way progress is reported</li>
 * <li>{@link #createContext(ITextViewer, int)} to provide the context object
 * passed to the computers</li>
 * <li>{@link #getContextInformationValidator()} to add context validation (needed if any
 * contexts are provided)</li>
 * <li>{@link #getErrorMessage()} to change error reporting</li>
 * </ul>
 * </p>
 * 
 * @base org.eclipse.jdt.internal.ui.text.java.ContentAssistProcessor
 */
public class StructuredContentAssistProcessor
        implements IContentAssistProcessor, IPropertyChangeListener, IReleasable {

    /** Legacy editor configuration extension point. */
    private static final String CONTENT_ASSIST_PROCESSOR_EXTENDED_ID = "contentassistprocessor"; //$NON-NLS-1$

    /** Content assist processors added through the now legacy editor configuration extension point */
    private List fLegacyExtendedContentAssistProcessors;

    /**
     * Dialog settings key for the "all categories are disabled" warning dialog. See
     * {@link OptionalMessageDialog}.
     */
    private static final String PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY = "EmptyDefaultAssistCategory"; //$NON-NLS-1$

    /**
     * Used to sort categories by their page order so they are cycled in the correct order
     */
    private final Comparator PAGE_ORDER_COMPARATOR = new Comparator() {
        public int compare(Object o1, Object o2) {
            CompletionProposalCategory d1 = (CompletionProposalCategory) o1;
            CompletionProposalCategory d2 = (CompletionProposalCategory) o2;

            return d1.getPageSortRank(fContentTypeID) - d2.getPageSortRank(fContentTypeID);
        }
    };

    /**
     * Used to sort categories by their default page order so they are
     * ordered correctly on the default page
     */
    private final Comparator DEFAULT_PAGE_ORDER_COMPARATOR = new Comparator() {
        public int compare(Object o1, Object o2) {
            CompletionProposalCategory d1 = (CompletionProposalCategory) o1;
            CompletionProposalCategory d2 = (CompletionProposalCategory) o2;

            return d1.getDefaultPageSortRank(fContentTypeID) - d2.getDefaultPageSortRank(fContentTypeID);
        }
    };

    /** List of {@link CompletionProposalCategory}s supported by this processor */
    private List fCategories;

    /** content type ID this processor is associated with */
    String fContentTypeID;

    /** partition type ID this processor is associated with */
    private final String fPartitionTypeID;

    /** Content assistant used for giving the user status messages and listening to completion results*/
    private ContentAssistant fAssistant;

    /* cycling stuff */
    private int fRepetition = -1;
    private List fCategoryIteration = null;
    private String fIterationGesture = null;
    private int fNumberOfComputedResults = 0;
    private String fErrorMessage;

    /** Optionally specified preference store for listening to property change events */
    private IPreferenceStore fPreferenceStore;

    /** The viewer this processor is associated with */
    private ITextViewer fViewer;

    /**
     * the {@link ITextInputListener} used to set the content type when
     * a document is set for this processors associated viewer.
     */
    private ITextInputListener fTextInputListener;

    private CompletionListener fCompletionListener;

    /** the context information validator for this processor */
    private IContextInformationValidator fContextInformationValidator;

    private AutoActivationDelegate fAutoActivation;

    /**
     * <p>Create a new content assist processor for a specific partition type. 
     * The content type will be determined when a document is set on the viewer</p>
     * 
     * <p>If the given {@link IPreferenceStore} is not <code>null</code> then this
     * processor will be registered as a {@link IPropertyChangeListener} on the given store
     * so that implementers of this class can change the way the processor acts based on
     * user preferences</p>
     * 
     * @param assistant {@link ContentAssistant} to use
     * @param partitionTypeID the partition type this processor is for
     * @param viewer {@link ITextViewer} this processor is acting in
     * @param preferenceStore This processor will be registered as a {@link IPropertyChangeListener}
     * on this store and the processor itself will take care of removing itself as a listener, if
     * <code>null</code> then will not be registered as a {@link IPropertyChangeListener}
     */
    public StructuredContentAssistProcessor(ContentAssistant assistant, String partitionTypeID, ITextViewer viewer,
            IPreferenceStore preferenceStore) {

        Assert.isNotNull(partitionTypeID);
        Assert.isNotNull(assistant);

        //be sure the registry has been loaded, none blocking
        CompletionProposalComputerRegistry.getDefault().initialize();

        //register on the preference store
        this.fPreferenceStore = preferenceStore;
        if (this.fPreferenceStore != null) {
            this.fPreferenceStore.addPropertyChangeListener(this);
        }

        //The content type can not be determined until a document has been set
        this.fContentTypeID = null;
        this.fViewer = viewer;
        if (viewer != null) {
            this.fTextInputListener = new TextInputListener();
            viewer.addTextInputListener(this.fTextInputListener);

            if (viewer.getDocument() != null) {
                /* it is highly unlike the document has already been set, but check
                 * just for sanity
                 */
                this.fTextInputListener.inputDocumentChanged(null, viewer.getDocument());
            }
        }

        //set the associated partition type
        this.fPartitionTypeID = partitionTypeID;

        //add completion listener
        fAssistant = assistant;
        fCompletionListener = new CompletionListener();
        fAssistant.addCompletionListener(fCompletionListener);

        //lazy load these to speed up initial editor opening
        fLegacyExtendedContentAssistProcessors = null;
        fCategories = null;
    }

    /**
     * <p>Collect the proposals using the extension points</p>
     * 
     * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
     */
    public final ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
        clearState();

        IProgressMonitor monitor = createProgressMonitor();
        monitor.beginTask(SSEUIMessages.ContentAssist_computing_proposals, getProposalCategories().size() + 1);

        CompletionProposalInvocationContext context = createContext(viewer, offset);

        monitor.subTask(SSEUIMessages.ContentAssist_collecting_proposals);
        List proposals = collectProposals(viewer, offset, monitor, context);

        monitor.subTask(SSEUIMessages.ContentAssist_sorting_proposals);
        List filtered = filterAndSortProposals(proposals, monitor, context);
        fNumberOfComputedResults = filtered.size();

        ICompletionProposal[] result = (ICompletionProposal[]) filtered
                .toArray(new ICompletionProposal[filtered.size()]);
        monitor.done();

        return result;
    }

    /**
     * <p>Collect the context information using the extension points</p>
     * 
     * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer, int)
     */
    public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
        clearState();

        IProgressMonitor monitor = createProgressMonitor();
        monitor.beginTask(SSEUIMessages.ContentAssist_computing_contexts, getProposalCategories().size() + 1);

        monitor.subTask(SSEUIMessages.ContentAssist_collecting_contexts);
        List proposals = collectContextInformation(viewer, offset, monitor);

        monitor.subTask(SSEUIMessages.ContentAssist_sorting_contexts);
        List filtered = filterAndSortContextInformation(proposals, monitor);
        fNumberOfComputedResults = filtered.size();

        IContextInformation[] result = (IContextInformation[]) filtered
                .toArray(new IContextInformation[filtered.size()]);
        monitor.done();
        return result;
    }

    /**
     * <p>Default implementation is to return <code>null</code></p>
     * <p>Extenders may override</p>
     * 
     * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
     */
    public char[] getCompletionProposalAutoActivationCharacters() {
        return (fAutoActivation != null) ? fAutoActivation.getCompletionProposalAutoActivationCharacters() : null;
    }

    /**
     * <p>Default implementation is to return <code>null</code></p>
     * <p>Extenders may override</p>
     * 
     * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
     */
    public char[] getContextInformationAutoActivationCharacters() {
        return (fAutoActivation != null) ? fAutoActivation.getContextInformationAutoActivationCharacters() : null;
    }

    /**
     * <p>Extenders may override this function to change error reporting</p>
     * 
     * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
     */
    public String getErrorMessage() {
        if (fErrorMessage != null)
            return fErrorMessage;
        if (fNumberOfComputedResults > 0)
            return null;
        return SSEUIMessages.ContentAssist_no_completions;
    }

    /**
     * @see org.eclipse.wst.sse.ui.contentassist.StructuredContentAssistProcessor#getContextInformationValidator()
     */
    public IContextInformationValidator getContextInformationValidator() {
        if (this.fContextInformationValidator == null) {
            this.fContextInformationValidator = new ContextInformationValidator();
        }
        return this.fContextInformationValidator;
    }

    public void install(ITextViewer viewer) {
        if (fPreferenceStore != null) {
            fPreferenceStore.addPropertyChangeListener(this);
        }
        if (fViewer != null) {
            fViewer.removeTextInputListener(fTextInputListener);
        }
        fViewer = viewer;
        if (fViewer != null) {
            if (fTextInputListener == null) {
                fTextInputListener = new TextInputListener();
            }
            fViewer.addTextInputListener(fTextInputListener);
        }
        if (fAssistant != null) {
            if (fCompletionListener == null) {
                fCompletionListener = new CompletionListener();
            }
            fAssistant.addCompletionListener(fCompletionListener);
        }
    }

    /**
     * <p>Extenders may override, but should always be sure to call the super implementation</p>
     * 
     * @see org.eclipse.wst.sse.ui.internal.IReleasable#release()
     */
    public void release() {
        if (fAutoActivation != null) {
            fAutoActivation.dispose();
            fAutoActivation = null;
        }
        if (this.fPreferenceStore != null) {
            this.fPreferenceStore.removePropertyChangeListener(this);
        }

        if (this.fViewer != null) {
            this.fViewer.removeTextInputListener(this.fTextInputListener);
            this.fViewer = null;
            this.fTextInputListener = null;
        }
        if (this.fAssistant != null) {
            this.fAssistant.removeCompletionListener(fCompletionListener);
            this.fCompletionListener = null;
        }
    }

    /**
     * <p>Intended to be overridden by extenders wishing to change the behavior
     * of the processor based on user preferences from the store optionally 
     * associated with this processor.  If no store was given to the constructor
     * when creating this assistant then this method will never be invoked.</p>
     * 
     * <p>The default implementation does not react to the events in any way</p>
     * 
     * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent event) {
    }

    /**
     * <p>Filters and sorts the proposals. The passed list may be modified
     * and returned, or a new list may be created and returned.</p>
     * 
     * <p>The default implementation does not do any sorting or filtering.</p>
     * <p>Extenders may override this function.</p>
     *
     * @param proposals the list of collected proposals (element type:
     *        {@link ICompletionProposal})
     * @param monitor a progress monitor
     * @param context TODO
     * @return the list of filtered and sorted proposals, ready for
     *         display (element type: {@link ICompletionProposal})
     */
    protected List filterAndSortProposals(List proposals, IProgressMonitor monitor,
            CompletionProposalInvocationContext context) {
        return proposals;
    }

    /**
     * <p>Filters and sorts the context information objects. The passed
     * list may be modified and returned, or a new list may be created
     * and returned.</p>
     * 
     * <p>The default implementation does not do any sorting or filtering</p>
     * <p>Extenders may override this method</p>
     *
     * @param contexts the list of collected proposals (element type:
     *        {@link IContextInformation})
     * @param monitor a progress monitor
     * @return the list of filtered and sorted proposals, ready for
     *         display (element type: {@link IContextInformation})
     */
    protected List filterAndSortContextInformation(List contexts, IProgressMonitor monitor) {
        return contexts;
    }

    /**
     * <p>Creates a progress monitor.</p>
     * <p>The default implementation creates a {@link NullProgressMonitor}.</p>
     *
     * <p>Extenders may override this method</p>
     *
     * @return a progress monitor
     */
    protected IProgressMonitor createProgressMonitor() {
        return new NullProgressMonitor();
    }

    /**
     * <p>Creates the context that is passed to the completion proposal
     * computers.</p>
     * 
     * <p>Extenders may override this method</p>
     *
     * @param viewer the viewer that content assist is invoked on
     * @param offset the content assist offset
     * @return the context to be passed to the computers
     */
    protected CompletionProposalInvocationContext createContext(ITextViewer viewer, int offset) {
        return new CompletionProposalInvocationContext(viewer, offset);
    }

    /**
     * @return the associated preference store
     */
    protected IPreferenceStore getPreferenceStore() {
        return this.fPreferenceStore;
    }

    /**
     * Clears the state
     */
    private void clearState() {
        fErrorMessage = null;
        fNumberOfComputedResults = 0;
    }

    /**
     * <p>Collects the proposals from the extensions.</p>
     *
     * @param viewer the text viewer
     * @param offset the offset
     * @param monitor the progress monitor
     * @param context the code assist invocation context
     * @return the list of proposals
     */
    private List collectProposals(ITextViewer viewer, int offset, IProgressMonitor monitor,
            CompletionProposalInvocationContext context) {
        List proposals = new ArrayList();
        List categories = getCategories();
        for (Iterator it = categories.iterator(); it.hasNext();) {
            CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
            List computed = cat.computeCompletionProposals(context, this.fContentTypeID, this.fPartitionTypeID,
                    new SubProgressMonitor(monitor, 1));
            proposals.addAll(computed);
            if (fErrorMessage == null) {
                fErrorMessage = cat.getErrorMessage();
            }
        }

        // if default page
        // Deal with adding in proposals from processors added through the legacy extension
        if (isFirstPage() && getLegacyExtendedContentAssistProcessors() != null
                && !getLegacyExtendedContentAssistProcessors().isEmpty()) {

            Iterator iter = getLegacyExtendedContentAssistProcessors().iterator();
            while (iter.hasNext()) {
                IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next();
                ICompletionProposal[] legacyComputed = null;
                try {
                    legacyComputed = legacyProcessor.computeCompletionProposals(viewer, offset);
                } catch (Exception e) {
                    Logger.logException("Problem occurred while gathering proposals from " //$NON-NLS-1$
                            + legacyProcessor.getClass().getName(), e);
                }
                if (legacyComputed != null) {
                    proposals.addAll(Arrays.asList(legacyComputed));
                }
            }
        }

        return proposals;
    }

    /**
     * <p>Collects the context information from the extensions.</p>
     * 
     * @param viewer
     * @param offset
     * @param monitor
     * @return
     */
    private List collectContextInformation(ITextViewer viewer, int offset, IProgressMonitor monitor) {
        List proposals = new ArrayList();
        CompletionProposalInvocationContext context = createContext(viewer, offset);

        List providers = getCategories();
        for (Iterator it = providers.iterator(); it.hasNext();) {
            CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
            List computed = cat.computeContextInformation(context, this.fContentTypeID, this.fPartitionTypeID,
                    new SubProgressMonitor(monitor, 1));
            proposals.addAll(computed);
            if (fErrorMessage == null) {
                fErrorMessage = cat.getErrorMessage();
            }
        }

        // Deal with adding in contexts from processors added through the legacy extension
        if (getLegacyExtendedContentAssistProcessors() != null
                && !getLegacyExtendedContentAssistProcessors().isEmpty()) {

            Iterator iter = getLegacyExtendedContentAssistProcessors().iterator();
            while (iter.hasNext()) {
                IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next();
                IContextInformation[] legacyComputed = legacyProcessor.computeContextInformation(viewer, offset);
                if (legacyComputed != null) {
                    proposals.addAll(Arrays.asList(legacyComputed));
                }
            }
        }

        return proposals;
    }

    /**
     * @return the next set of categories
     */
    private List getCategories() {
        List categories;
        if (fCategoryIteration == null) {
            categories = getProposalCategories();
        } else {
            int iteration = fRepetition % fCategoryIteration.size();
            fAssistant.setStatusMessage(createIterationMessage());
            fAssistant.setEmptyMessage(createEmptyMessage());
            fRepetition++;

            categories = (List) fCategoryIteration.get(iteration);
        }

        return categories;
    }

    /**
     *  This may show the warning dialog if all categories are disabled
     */
    private void resetCategoryIteration() {
        fCategoryIteration = getCategoryIteration();
    }

    /**
     * @return {@link List} of {@link List}s of {@link CompletionProposalCategory}s, this is
     * the ordered list of the completion categories to cycle through
     */
    private List getCategoryIteration() {
        List sequence = new ArrayList();
        sequence.add(getDefaultCategories());
        for (Iterator it = getSortedOwnPageCategories().iterator(); it.hasNext();) {
            CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
            sequence.add(Collections.singletonList(cat));
        }
        return sequence;
    }

    /**
     * @return the sorted categories for the default page
     */
    private List getDefaultCategories() {
        // default mix - enable all included computers
        List included = getDefaultCategoriesUnchecked();

        if (included.size() == 0 && CompletionProposalComputerRegistry.getDefault().hasUninstalledComputers()) {
            if (informUserAboutEmptyDefaultCategory()) {
                // preferences were restored - recompute the default categories
                included = getDefaultCategoriesUnchecked();
            }
            CompletionProposalComputerRegistry.getDefault().resetUnistalledComputers();
        }

        Collections.sort(included, DEFAULT_PAGE_ORDER_COMPARATOR);

        return included;
    }

    /**
     * <p>Gets the default categories with no error checking.</p>
     * 
     * @return the default {@link CompletionProposalCategory}s
     */
    private List getDefaultCategoriesUnchecked() {
        List included = new ArrayList();
        for (Iterator it = getProposalCategories().iterator(); it.hasNext();) {
            CompletionProposalCategory category = (CompletionProposalCategory) it.next();
            if (category.isIncludedOnDefaultPage(this.fContentTypeID)
                    && category.hasComputers(fContentTypeID, fPartitionTypeID))
                included.add(category);
        }
        return included;
    }

    /**
     * <p>Informs the user about the fact that there are no enabled categories in the default content
     * assist set and shows a link to the preferences.</p>
     *
     * @return  <code>true</code> if the default should be restored
     */
    private boolean informUserAboutEmptyDefaultCategory() {
        /*If warn about empty default category and there are associated properties for this
         * processors content type and those properties have an associated properties page then
         * display warning message to user.
         */
        ICompletionProposalCategoriesConfigurationReader properties = CompletionProposoalCatigoriesConfigurationRegistry
                .getDefault().getReadableConfiguration(this.fContentTypeID);
        if (OptionalMessageDialog.isDialogEnabled(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY)
                && properties instanceof ICompletionProposalCategoriesConfigurationWriter
                && ((ICompletionProposalCategoriesConfigurationWriter) properties).hasAssociatedPropertiesPage()) {

            ICompletionProposalCategoriesConfigurationWriter propertiesExtension = (ICompletionProposalCategoriesConfigurationWriter) properties;

            final Shell shell = SSEUIPlugin.getActiveWorkbenchShell();
            String title = SSEUIMessages.ContentAssist_all_disabled_title;
            String message = SSEUIMessages.ContentAssist_all_disabled_message;
            // see PreferencePage#createControl for the 'defaults' label
            final String restoreButtonLabel = JFaceResources.getString("defaults"); //$NON-NLS-1$
            final String linkMessage = NLS.bind(SSEUIMessages.ContentAssist_all_disabled_preference_link,
                    LegacyActionTools.removeMnemonics(restoreButtonLabel));
            final int restoreId = IDialogConstants.CLIENT_ID + 10;
            final int settingsId = IDialogConstants.CLIENT_ID + 11;
            final OptionalMessageDialog dialog = new OptionalMessageDialog(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY,
                    shell, title, null /* default image */, message, MessageDialog.WARNING,
                    new String[] { restoreButtonLabel, IDialogConstants.CLOSE_LABEL }, 1) {
                /*
                 * @see org.eclipse.jdt.internal.ui.dialogs.OptionalMessageDialog#createCustomArea(org.eclipse.swt.widgets.Composite)
                 */
                protected Control createCustomArea(Composite composite) {
                    // wrap link and checkbox in one composite without space
                    Composite parent = new Composite(composite, SWT.NONE);
                    GridLayout layout = new GridLayout();
                    layout.marginHeight = 0;
                    layout.marginWidth = 0;
                    layout.verticalSpacing = 0;
                    parent.setLayout(layout);

                    Composite linkComposite = new Composite(parent, SWT.NONE);
                    layout = new GridLayout();
                    layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
                    layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
                    layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
                    linkComposite.setLayout(layout);

                    Link link = new Link(linkComposite, SWT.NONE);
                    link.setText(linkMessage);
                    link.addSelectionListener(new SelectionAdapter() {
                        public void widgetSelected(SelectionEvent e) {
                            setReturnCode(settingsId);
                            close();
                        }
                    });
                    GridData gridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
                    gridData.widthHint = this.getMinimumMessageWidth();
                    link.setLayoutData(gridData);

                    // create checkbox and "don't show this message" prompt
                    super.createCustomArea(parent);

                    return parent;
                }

                /*
                 * @see org.eclipse.jface.dialogs.MessageDialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
                 */
                protected void createButtonsForButtonBar(Composite parent) {
                    Button[] buttons = new Button[2];
                    buttons[0] = createButton(parent, restoreId, restoreButtonLabel, false);
                    buttons[1] = createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL,
                            true);
                    setButtons(buttons);
                }
            };
            int returnValue = dialog.open();

            //based on user actions either reset defaults or open preference dialog
            if (restoreId == returnValue || settingsId == returnValue) {
                if (restoreId == returnValue) {
                    propertiesExtension.loadDefaults();
                    propertiesExtension.saveConfiguration();
                }
                if (settingsId == returnValue) {
                    PreferencesUtil
                            .createPreferenceDialogOn(shell, propertiesExtension.getPropertiesPageID(), null, null)
                            .open();
                }

                return true;
            }
        }
        return false;
    }

    /**
     * @return a sorted {@link List} of {@link CompletionProposalCategory}s that
     * should be displayed on their own content assist page
     */
    private List getSortedOwnPageCategories() {
        ArrayList sorted = new ArrayList();
        for (Iterator it = getProposalCategories().iterator(); it.hasNext();) {
            CompletionProposalCategory category = (CompletionProposalCategory) it.next();
            if (category.isDisplayedOnOwnPage(this.fContentTypeID)
                    && category.hasComputers(fContentTypeID, fPartitionTypeID)) {

                sorted.add(category);
            }
        }
        Collections.sort(sorted, PAGE_ORDER_COMPARATOR);
        return sorted;
    }

    /**
     * @return a user message describing that there are no content assist suggestions for the current page
     */
    private String createEmptyMessage() {
        return NLS.bind(SSEUIMessages.ContentAssist_no_message, new String[] { getCategoryLabel(fRepetition) });
    }

    /**
     * @return user message describing what the next page of content assist holds
     */
    private String createIterationMessage() {
        return NLS.bind(SSEUIMessages.ContentAssist_toggle_affordance_update_message, new String[] {
                getCategoryLabel(fRepetition), fIterationGesture, getCategoryLabel(fRepetition + 1) });
    }

    /**
     * @param repetition which category to get the label for
     * @return the label of the category
     */
    private String getCategoryLabel(int repetition) {
        int iteration = (fCategoryIteration != null ? repetition % fCategoryIteration.size() : 0);
        if (iteration == 0)
            return SSEUIMessages.ContentAssist_defaultProposalCategory_title;
        return ((CompletionProposalCategory) ((List) fCategoryIteration.get(iteration)).get(0)).getDisplayName();
    }

    /**
     * @return {@link String} representing the user command to iterate to the next page
     */
    private String getIterationGesture() {
        TriggerSequence binding = getIterationBinding();
        return binding != null ? NLS.bind(SSEUIMessages.ContentAssist_press, new Object[] { binding.format() })
                : SSEUIMessages.ContentAssist_click;
    }

    /**
     * @return {@link KeySequence} used by user to iterate to the next page
     */
    private KeySequence getIterationBinding() {
        final IBindingService bindingSvc = (IBindingService) PlatformUI.getWorkbench()
                .getAdapter(IBindingService.class);
        TriggerSequence binding = bindingSvc
                .getBestActiveBindingFor(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
        if (binding instanceof KeySequence)
            return (KeySequence) binding;
        return null;
    }

    /**
     * @return <code>true</code> if displaying first page, <code>false</code> otherwise
     */
    private boolean isFirstPage() {
        return fCategoryIteration == null || fCategoryIteration.size() == 1
                || fRepetition % fCategoryIteration.size() == 1;
    }

    /**
     * <p><b>NOTE: </b>This method should be used over accessing the
     * {@link #fLegacyExtendedContentAssistProcessors} field directly so as to
     * facilitate the lazy initialization of the field.</p>
     * 
     * @return the legacy extended content assist processors
     */
    private List getLegacyExtendedContentAssistProcessors() {
        if (fLegacyExtendedContentAssistProcessors == null) {
            fLegacyExtendedContentAssistProcessors = ExtendedConfigurationBuilder.getInstance()
                    .getConfigurations(CONTENT_ASSIST_PROCESSOR_EXTENDED_ID, fPartitionTypeID);
        }

        return fLegacyExtendedContentAssistProcessors;
    }

    /**
     * <p><b>NOTE: </b>This method should be used over accessing the {@link #fCategories}
     * field directly so as to facilitate the lazy initialization of the field.</p>
     * 
     * @return the categories associated with the content type this processor is associated with
     */
    private List getProposalCategories() {
        if (fCategories == null) {
            fCategories = CompletionProposalComputerRegistry.getDefault().getProposalCategories(fContentTypeID);
        }

        return fCategories;
    }

    /**
     * The completion listener class for this processor.
     */
    private final class CompletionListener implements ICompletionListener, ICompletionListenerExtension {
        /**
         * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
         */
        public void assistSessionStarted(ContentAssistEvent event) {
            if (event.processor == StructuredContentAssistProcessor.this
                    || (event.processor instanceof CompoundContentAssistProcessor
                            && ((CompoundContentAssistProcessor) event.processor)
                                    .containsProcessor(StructuredContentAssistProcessor.this))) {

                fIterationGesture = getIterationGesture();
                KeySequence binding = getIterationBinding();

                // This may show the warning dialog if all categories are disabled
                resetCategoryIteration();
                for (Iterator it = StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it
                        .hasNext();) {
                    CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
                    cat.sessionStarted();
                }

                fRepetition = 0;
                if (event.assistant instanceof IContentAssistantExtension2) {
                    IContentAssistantExtension2 extension = (IContentAssistantExtension2) event.assistant;

                    if (fCategoryIteration.size() == 1) {
                        extension.setRepeatedInvocationMode(false);
                        extension.setShowEmptyList(false);
                    } else {
                        extension.setRepeatedInvocationMode(true);
                        extension.setStatusLineVisible(true);
                        extension.setStatusMessage(createIterationMessage());
                        extension.setShowEmptyList(true);
                        if (extension instanceof IContentAssistantExtension3) {
                            IContentAssistantExtension3 ext3 = (IContentAssistantExtension3) extension;
                            ((ContentAssistant) ext3).setRepeatedInvocationTrigger(binding);
                        }
                    }
                }
            }
        }

        /**
         * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent)
         */
        public void assistSessionEnded(ContentAssistEvent event) {
            if (event.processor == StructuredContentAssistProcessor.this
                    || (event.processor instanceof CompoundContentAssistProcessor
                            && ((CompoundContentAssistProcessor) event.processor)
                                    .containsProcessor(StructuredContentAssistProcessor.this))) {
                for (Iterator it = StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it
                        .hasNext();) {
                    CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
                    cat.sessionEnded();
                }

                fCategoryIteration = null;
                fRepetition = -1;
                fIterationGesture = null;
                if (event.assistant instanceof IContentAssistantExtension2) {
                    IContentAssistantExtension2 extension = (IContentAssistantExtension2) event.assistant;
                    extension.setShowEmptyList(false);
                    extension.setRepeatedInvocationMode(false);
                    extension.setStatusLineVisible(false);
                    if (extension instanceof IContentAssistantExtension3) {
                        IContentAssistantExtension3 ext3 = (IContentAssistantExtension3) extension;
                        ((ContentAssistant) ext3).setRepeatedInvocationTrigger(null);
                    }
                }
            }
        }

        /**
         * @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal, boolean)
         */
        public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
            //ignore
        }

        /**
         * @see org.eclipse.jface.text.contentassist.ICompletionListenerExtension#assistSessionRestarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
         */
        public void assistSessionRestarted(ContentAssistEvent event) {
            fRepetition = 0;
        }
    }

    /**
     * 
     */
    private class TextInputListener implements ITextInputListener {

        /**
         * <p>Set the content type based on the new document if it has not already been
         * set yet.</p>
         * 
         * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
         */
        public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
            if (fContentTypeID == null) {
                if (newInput instanceof IStructuredDocument) {
                    IStructuredModel model = null;
                    try {
                        model = StructuredModelManager.getModelManager()
                                .getModelForRead((IStructuredDocument) newInput);
                        if (model != null) {
                            fContentTypeID = model.getContentTypeIdentifier();
                            if (fAutoActivation != null) {
                                fAutoActivation.dispose();
                            }
                            fAutoActivation = CompletionProposalComputerRegistry.getDefault()
                                    .getActivator(fContentTypeID, fPartitionTypeID);
                        }
                    } finally {
                        if (model != null) {
                            model.releaseFromRead();
                        }
                    }
                }
            }
        }

        /**
         * <p>Ignored</p>
         * 
         * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
         */
        public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
            //ignore
        }
    }

    protected void setAutoActivationDelay(int delay) {
        fAssistant.setAutoActivationDelay(delay);
    }

}